diff --git a/examples/example.rs b/examples/example.rs index d5fcd30..a3d9548 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,7 +1,7 @@ use std::net::{IpAddr, Ipv4Addr}; use bdk_kyoto::builder::{LightClientBuilder, ServiceFlags, TrustedPeer}; -use bdk_kyoto::{LightClient, RequesterExt}; +use bdk_kyoto::{LightClient, RequesterExt, ScanType}; use bdk_wallet::bitcoin::Network; use bdk_wallet::{KeychainKind, Wallet}; use tokio::select; @@ -33,6 +33,11 @@ async fn main() -> anyhow::Result<()> { .lookahead(30) .create_wallet_no_persist()?; + // With no persistence, each scan type is a recovery. + let scan_type = ScanType::Recovery { + from_height: 170_000, + }; + // The light client builder handles the logic of inserting the SPKs let LightClient { requester, @@ -41,7 +46,7 @@ async fn main() -> anyhow::Result<()> { mut update_subscriber, node, } = LightClientBuilder::new() - .scan_after(170_000) + .scan_type(scan_type) .peers(peers) .build(&wallet) .unwrap(); diff --git a/src/builder.rs b/src/builder.rs index 09dfdbd..d912bc9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -14,7 +14,7 @@ //! use bdk_wallet::Wallet; //! use bdk_wallet::bitcoin::Network; //! use bdk_kyoto::builder::{LightClientBuilder, TrustedPeer}; -//! use bdk_kyoto::LightClient; +//! use bdk_kyoto::{LightClient, ScanType}; //! //! #[tokio::main] //! async fn main() -> anyhow::Result<()> { @@ -29,9 +29,11 @@ //! .network(Network::Signet) //! .create_wallet_no_persist()?; //! +//! let scan_type = ScanType::Recovery { from_height: 200_000 }; +//! //! let LightClient { requester, log_subscriber, warning_subscriber, update_subscriber, node } = LightClientBuilder::new() -//! // When recovering a user's wallet, specify a height to start at -//! .scan_after(200_000) +//! // Configure the scan to recover the wallet +//! .scan_type(scan_type) //! // A node may handle mutliple connections //! .connections(2) //! // Choose where to store node data @@ -53,7 +55,7 @@ pub use kyoto::{ TrustedPeer, }; -use crate::{LightClient, LogSubscriber, UpdateSubscriber, WalletExt, WarningSubscriber}; +use crate::{LightClient, LogSubscriber, ScanType, UpdateSubscriber, WalletExt, WarningSubscriber}; const RECOMMENDED_PEERS: u8 = 2; @@ -62,7 +64,7 @@ const RECOMMENDED_PEERS: u8 = 2; pub struct LightClientBuilder { peers: Option>, connections: Option, - birthday_height: Option, + scan_type: ScanType, data_dir: Option, timeout: Option, } @@ -73,7 +75,7 @@ impl LightClientBuilder { Self { peers: None, connections: None, - birthday_height: None, + scan_type: ScanType::default(), data_dir: None, timeout: None, } @@ -99,8 +101,8 @@ impl LightClientBuilder { /// Add a wallet "birthday", or block to start searching for transactions _strictly after_. /// Only useful for recovering wallets. If the wallet has a tip that is already higher than the /// height provided, this height will be ignored. - pub fn scan_after(mut self, height: u32) -> Self { - self.birthday_height = Some(height); + pub fn scan_type(mut self, scan_type: ScanType) -> Self { + self.scan_type = scan_type; self } @@ -117,32 +119,25 @@ impl LightClientBuilder { if let Some(whitelist) = self.peers { node_builder = node_builder.add_peers(whitelist); } - match self.birthday_height { - Some(birthday) => { - // If there is a birthday at a height less than our local chain, we may assume we've - // already synced the wallet past the birthday height and no longer - // need it. - if birthday < wallet.local_chain().tip().height() { - let block_id = wallet.local_chain().tip(); - let header_cp = HeaderCheckpoint::new(block_id.height(), block_id.hash()); - node_builder = node_builder.anchor_checkpoint(header_cp) - } else { - let cp = HeaderCheckpoint::closest_checkpoint_below_height(birthday, network); - node_builder = node_builder.anchor_checkpoint(cp) - } - } - None => { - // If there is no birthday provided and the local chain starts at the genesis block, - // we assume this is a new wallet and use the most recent - // checkpoint. Otherwise we sync from the last known tip in the - // LocalChain. + match self.scan_type { + // This is a no-op because kyoto will start from the latest checkpoint if none is + // provided + ScanType::New => (), + ScanType::Sync => { let block_id = wallet.local_chain().tip(); - if block_id.height() > 0 { - let header_cp = HeaderCheckpoint::new(block_id.height(), block_id.hash()); - node_builder = node_builder.anchor_checkpoint(header_cp) - } + let header_cp = HeaderCheckpoint::new(block_id.height(), block_id.hash()); + node_builder = node_builder.anchor_checkpoint(header_cp); } - } + ScanType::Recovery { from_height } => { + // Make sure we don't miss the first transaction of the wallet. + // The anchor checkpoint is non-inclusive. + let birthday = from_height.saturating_sub(1); + let header_cp = + HeaderCheckpoint::closest_checkpoint_below_height(birthday, network); + node_builder = node_builder.anchor_checkpoint(header_cp); + } + }; + if let Some(dir) = self.data_dir { node_builder = node_builder.add_data_dir(dir); } diff --git a/src/lib.rs b/src/lib.rs index fb7b354..de7f881 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,6 +195,21 @@ impl WarningSubscriber { } } +/// How to scan compact block filters on start up. +#[derive(Debug, Clone, Copy, Default)] +pub enum ScanType { + /// Start a wallet sync that is known to have no transactions. + New, + /// Sync the wallet from the last known wallet checkpoint to the rest of the network. + #[default] + Sync, + /// Recover an old wallet by scanning after the specified height. + Recovery { + /// The height in the block chain to begin searching for transactions. + from_height: u32, + }, +} + /// Extend the functionality of [`Wallet`](bdk_wallet) for interoperablility /// with the light client. pub trait WalletExt {