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:
Antoine Poinsot 2022-11-09 17:59:31 +01:00
parent c32f714a2e
commit bd4de0b87a
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
3 changed files with 109 additions and 4 deletions

View File

@ -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", &params!(Json::Array(descriptors)));
return None;
}
let res = self.make_wallet_request("importdescriptors", &params!(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.

View File

@ -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

View File

@ -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 {