Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make scanning requirements explicit #100

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions examples/example.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down
70 changes: 36 additions & 34 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand All @@ -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
Expand All @@ -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;

Expand All @@ -62,7 +64,7 @@ const RECOMMENDED_PEERS: u8 = 2;
pub struct LightClientBuilder {
peers: Option<Vec<TrustedPeer>>,
connections: Option<u8>,
birthday_height: Option<u32>,
scan_type: ScanType,
data_dir: Option<PathBuf>,
timeout: Option<Duration>,
}
Expand All @@ -73,7 +75,7 @@ impl LightClientBuilder {
Self {
peers: None,
connections: None,
birthday_height: None,
scan_type: ScanType::default(),
data_dir: None,
timeout: None,
}
Expand All @@ -96,11 +98,18 @@ impl LightClientBuilder {
self
}

/// 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.
/// Add a wallet "birthday", or block to start searching for transactions.
/// Implicitly sets the [`ScanType`] to `ScanType::Recovery`.
pub fn scan_after(mut self, height: u32) -> Self {
self.birthday_height = Some(height);
self.scan_type = ScanType::Recovery {
from_height: height,
};
self
}

/// Set the [`ScanType`] to sync, recover or start a new wallet.
pub fn scan_type(mut self, scan_type: ScanType) -> Self {
self.scan_type = scan_type;
self
}

Expand All @@ -117,32 +126,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);
}
Expand Down
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading