bitcoin: interface for rescanning the chain on the Bitcoin backend
For now we are given a timestamp and use 'importdescriptors'. It might be better to be passed a height and use 'rescanblockchain' instead.
This commit is contained in:
parent
c32f714a2e
commit
bd4de0b87a
@ -1,7 +1,9 @@
|
||||
///! Implementation of the Bitcoin interface using bitcoind.
|
||||
///!
|
||||
///! We use the RPC interface and a watchonly descriptor wallet.
|
||||
mod utils;
|
||||
use crate::{bitcoin::BlockChainTip, config, descriptors::MultipathDescriptor};
|
||||
use utils::block_before_date;
|
||||
|
||||
use std::{collections::HashSet, convert::TryInto, fs, io, str::FromStr, time::Duration};
|
||||
|
||||
@ -391,19 +393,34 @@ impl BitcoinD {
|
||||
}
|
||||
|
||||
// Import the receive and change descriptors from the multipath descriptor to bitcoind.
|
||||
fn import_descriptor(&self, desc: &MultipathDescriptor) -> Option<String> {
|
||||
// An optional timestamp may be given to rescan the chain from this date for this descriptor.
|
||||
fn import_descriptor(
|
||||
&self,
|
||||
desc: &MultipathDescriptor,
|
||||
timestamp: Option<u32>,
|
||||
) -> Option<String> {
|
||||
let descriptors = [desc.receive_descriptor(), desc.change_descriptor()]
|
||||
.iter()
|
||||
.map(|desc| {
|
||||
// TODO: rescan feature will probably need another timestamp than 'now'
|
||||
serde_json::json!({
|
||||
"desc": desc.to_string(),
|
||||
"timestamp": "now",
|
||||
"timestamp": timestamp.map(Json::from).unwrap_or_else(|| "now".into()),
|
||||
"active": false,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
// If this will trigger a rescan, do not wait for the response.
|
||||
if timestamp.is_some() {
|
||||
// TODO: should we check there was not timeout when writing the request on the
|
||||
// TcpStream in the SimpleHttpTransport implem?
|
||||
// NOTE: if the rescan gets aborted through the 'abortrescan' RPC we won't see the
|
||||
// error and bitcoind will keep the new timestamps for the descriptors as if it had
|
||||
// successfully rescanned them.
|
||||
self.make_noreply_request("importdescriptors", ¶ms!(Json::Array(descriptors)));
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = self.make_wallet_request("importdescriptors", ¶ms!(Json::Array(descriptors)));
|
||||
let all_succeeded = res
|
||||
.as_array()
|
||||
@ -459,7 +476,7 @@ impl BitcoinD {
|
||||
if let Some(err) = self.create_wallet(self.watchonly_wallet_path.clone()) {
|
||||
return Err(BitcoindError::WalletCreation(err));
|
||||
}
|
||||
if let Some(err) = self.import_descriptor(main_descriptor) {
|
||||
if let Some(err) = self.import_descriptor(main_descriptor, None) {
|
||||
return Err(BitcoindError::DescriptorImport(err));
|
||||
}
|
||||
|
||||
@ -754,6 +771,30 @@ impl BitcoinD {
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_rescan(&self, desc: &MultipathDescriptor, timestamp: u32) {
|
||||
self.import_descriptor(desc, Some(timestamp));
|
||||
}
|
||||
|
||||
/// Get the progress of the ongoing rescan, if there is any.
|
||||
pub fn rescan_progress(&self) -> Option<f64> {
|
||||
self.make_wallet_request("getwalletinfo", &[])
|
||||
.get("scanning")
|
||||
// If no rescan is ongoing, it will fail cause it would be 'false'
|
||||
.and_then(Json::as_object)
|
||||
.and_then(|map| map.get("progress"))
|
||||
.and_then(Json::as_f64)
|
||||
}
|
||||
|
||||
/// Get the height and hash of the last block with a timestamp below the given one.
|
||||
pub fn tip_before_timestamp(&self, timestamp: u32) -> Option<BlockChainTip> {
|
||||
block_before_date(
|
||||
timestamp,
|
||||
self.chain_tip(),
|
||||
|h| self.get_block_hash(h),
|
||||
|h| self.get_block_stats(h),
|
||||
)
|
||||
}
|
||||
}
|
||||
// Bitcoind uses a guess for the value of verificationprogress. It will eventually get to
|
||||
// be 1, and we want to be less conservative.
|
||||
|
||||
@ -55,6 +55,9 @@ pub trait BitcoinInterface: Send {
|
||||
/// Get the best block info.
|
||||
fn chain_tip(&self) -> BlockChainTip;
|
||||
|
||||
/// Get the timestamp set in the best block's header.
|
||||
fn tip_time(&self) -> u32;
|
||||
|
||||
/// Check whether this former tip is part of the current best chain.
|
||||
fn is_in_chain(&self, tip: &BlockChainTip) -> bool;
|
||||
|
||||
@ -88,6 +91,17 @@ pub trait BitcoinInterface: Send {
|
||||
|
||||
/// Broadcast this transaction to the Bitcoin P2P network
|
||||
fn broadcast_tx(&self, tx: &bitcoin::Transaction) -> Result<(), BitcoinError>;
|
||||
|
||||
/// Trigger a rescan of the block chain for transactions related to this descriptor since
|
||||
/// the given date.
|
||||
fn start_rescan(&self, desc: &descriptors::MultipathDescriptor, timestamp: u32);
|
||||
|
||||
/// Rescan progress percentage. Between 0 and 1.
|
||||
fn rescan_progress(&self) -> Option<f64>;
|
||||
|
||||
/// Get the last block chain tip with a timestamp below this. Timestamp must be a valid block
|
||||
/// timestamp.
|
||||
fn block_before_date(&self, timestamp: u32) -> Option<BlockChainTip>;
|
||||
}
|
||||
|
||||
impl BitcoinInterface for d::BitcoinD {
|
||||
@ -286,6 +300,24 @@ impl BitcoinInterface for d::BitcoinD {
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_rescan(&self, desc: &descriptors::MultipathDescriptor, timestamp: u32) {
|
||||
// FIXME: in theory i think this could potentially fail to actually start the rescan.
|
||||
self.start_rescan(desc, timestamp);
|
||||
}
|
||||
|
||||
fn rescan_progress(&self) -> Option<f64> {
|
||||
self.rescan_progress()
|
||||
}
|
||||
|
||||
fn block_before_date(&self, timestamp: u32) -> Option<BlockChainTip> {
|
||||
self.tip_before_timestamp(timestamp)
|
||||
}
|
||||
|
||||
fn tip_time(&self) -> u32 {
|
||||
let tip = self.chain_tip();
|
||||
self.get_block_stats(tip.hash).time
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: do we need to repeat the entire trait implemenation? Isn't there a nicer way?
|
||||
@ -342,6 +374,22 @@ impl BitcoinInterface for sync::Arc<sync::Mutex<dyn BitcoinInterface + 'static>>
|
||||
fn broadcast_tx(&self, tx: &bitcoin::Transaction) -> Result<(), BitcoinError> {
|
||||
self.lock().unwrap().broadcast_tx(tx)
|
||||
}
|
||||
|
||||
fn start_rescan(&self, desc: &descriptors::MultipathDescriptor, timestamp: u32) {
|
||||
self.lock().unwrap().start_rescan(desc, timestamp)
|
||||
}
|
||||
|
||||
fn rescan_progress(&self) -> Option<f64> {
|
||||
self.lock().unwrap().rescan_progress()
|
||||
}
|
||||
|
||||
fn block_before_date(&self, timestamp: u32) -> Option<BlockChainTip> {
|
||||
self.lock().unwrap().block_before_date(timestamp)
|
||||
}
|
||||
|
||||
fn tip_time(&self) -> u32 {
|
||||
self.lock().unwrap().tip_time()
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: We could avoid this type (and all the conversions entailing allocations) if bitcoind
|
||||
|
||||
@ -74,6 +74,22 @@ impl BitcoinInterface for DummyBitcoind {
|
||||
fn broadcast_tx(&self, _: &bitcoin::Transaction) -> Result<(), BitcoinError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn start_rescan(&self, _: &descriptors::MultipathDescriptor, _: u32) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn rescan_progress(&self) -> Option<f64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_before_date(&self, _: u32) -> Option<BlockChainTip> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn tip_time(&self) -> u32 {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DummyDb {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user