Skip to content

Commit 1c532fc

Browse files
authored
Merge pull request #152 from rustaceanrob/26-3-6-multiple-wallets
Multiple wallet syncing
2 parents c07c1d8 + e246909 commit 1c532fc

4 files changed

Lines changed: 432 additions & 45 deletions

File tree

examples/multi.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use bdk_kyoto::builder::{Builder, BuilderExt};
2+
use bdk_kyoto::{Info, Receiver, ScanType, UnboundedReceiver, Warning};
3+
use bdk_wallet::bitcoin::Network;
4+
use bdk_wallet::chain::DescriptorExt;
5+
use bdk_wallet::{KeychainKind, Wallet};
6+
use tokio::select;
7+
8+
const RECV_ONE: &str = "wpkh([9122d9e0/84'/1'/0']tpubDCYVtmaSaDzTxcgvoP5AHZNbZKZzrvoNH9KARep88vESc6MxRqAp4LmePc2eeGX6XUxBcdhAmkthWTDqygPz2wLAyHWisD299Lkdrj5egY6/0/*)";
9+
const CHANGE_ONE: &str = "wpkh([9122d9e0/84'/1'/0']tpubDCYVtmaSaDzTxcgvoP5AHZNbZKZzrvoNH9KARep88vESc6MxRqAp4LmePc2eeGX6XUxBcdhAmkthWTDqygPz2wLAyHWisD299Lkdrj5egY6/1/*)";
10+
const RECV_TWO: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/0/*)";
11+
const CHANGE_TWO: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/1/*)";
12+
const NETWORK: Network = Network::Signet;
13+
14+
/* Sync multiple BDK wallets */
15+
16+
async fn traces(
17+
mut info_subscriber: Receiver<Info>,
18+
mut warning_subscriber: UnboundedReceiver<Warning>,
19+
) {
20+
loop {
21+
select! {
22+
info = info_subscriber.recv() => {
23+
if let Some(info) = info {
24+
match info {
25+
Info::Progress(p) => {
26+
tracing::info!("chain height: {}, filter download progress: {}%", p.chain_height(), p.percentage_complete());
27+
},
28+
Info::BlockReceived(b) => {
29+
tracing::info!("downloaded block: {b}");
30+
},
31+
_ => (),
32+
}
33+
}
34+
}
35+
warn = warning_subscriber.recv() => {
36+
if let Some(warn) = warn {
37+
tracing::warn!("{warn}")
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
#[tokio::main]
45+
async fn main() -> anyhow::Result<()> {
46+
let subscriber = tracing_subscriber::FmtSubscriber::new();
47+
tracing::subscriber::set_global_default(subscriber)?;
48+
49+
let mut wallet_one = Wallet::create(RECV_ONE, CHANGE_ONE)
50+
.network(NETWORK)
51+
.create_wallet_no_persist()?;
52+
53+
let mut wallet_two = Wallet::create(RECV_TWO, CHANGE_TWO)
54+
.network(NETWORK)
55+
.create_wallet_no_persist()?;
56+
57+
// Build a request to sync for each wallet and place them in a vector.
58+
let wallet_iter = vec![(&wallet_one, ScanType::Sync), (&wallet_two, ScanType::Sync)];
59+
60+
// Now build a client that will sync both wallets simultaneously
61+
let client = Builder::new(NETWORK)
62+
.build_with_wallets(wallet_iter)
63+
.unwrap();
64+
let (client, logging, mut update_subscriber) = client.subscribe();
65+
tokio::task::spawn(
66+
async move { traces(logging.info_subscriber, logging.warning_subscriber).await },
67+
);
68+
let client = client.start();
69+
let requester = client.requester();
70+
71+
// Sync and apply updates. We can do this in a continual loop while the "application" is running.
72+
// Often this would occur on a separate thread than the underlying application user interface.
73+
loop {
74+
// Updates are grouped with the `DescriptorId` of the public, external descriptor.
75+
let updates = update_subscriber.updates().await?;
76+
for (desc_id, update) in updates {
77+
if wallet_one
78+
.public_descriptor(KeychainKind::External)
79+
.descriptor_id()
80+
.eq(&desc_id)
81+
{
82+
wallet_one.apply_update(update)?;
83+
tracing::info!("Wallet one summary: ");
84+
tracing::info!("Balance: {:#}", wallet_one.balance().total());
85+
tracing::info!(
86+
"Local chain tip: {}",
87+
wallet_one.local_chain().tip().height()
88+
);
89+
} else if wallet_two
90+
.public_descriptor(KeychainKind::External)
91+
.descriptor_id()
92+
.eq(&desc_id)
93+
{
94+
wallet_two.apply_update(update)?;
95+
tracing::info!("Wallet two summary: ");
96+
tracing::info!("Balance: {:#}", wallet_two.balance().total());
97+
tracing::info!(
98+
"Local chain tip: {}",
99+
wallet_two.local_chain().tip().height()
100+
);
101+
}
102+
}
103+
let fee_filter = requester.broadcast_min_feerate().await.unwrap();
104+
tracing::info!("Broadcast minimum fee rate: {:#}", fee_filter);
105+
tracing::info!("Press CTRL + C to exit.");
106+
}
107+
}

src/builder.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,11 @@
4747
use std::fmt::Display;
4848

4949
use bdk_wallet::{
50-
chain::{CheckPoint, IndexedTxGraph},
51-
Wallet,
50+
chain::{
51+
keychain_txout::KeychainTxOutIndex, CheckPoint, ConfirmationBlockTime, DescriptorExt,
52+
DescriptorId, IndexedTxGraph,
53+
},
54+
KeychainKind, Wallet,
5255
};
5356
pub use bip157::Builder;
5457
use bip157::{chain::ChainState, HeaderCheckpoint};
@@ -64,15 +67,21 @@ pub trait BuilderExt {
6467
self,
6568
wallet: &Wallet,
6669
scan_type: ScanType,
67-
) -> Result<LightClient<Idle>, BuilderError>;
70+
) -> Result<LightClient<Idle, crate::wallets::Single>, BuilderError>;
71+
72+
/// Attempt to build the node with scripts from multiple [`Wallet`]s and following a [`ScanType`].
73+
fn build_with_wallets(
74+
self,
75+
wallets: Vec<(&Wallet, ScanType)>,
76+
) -> Result<LightClient<Idle, crate::wallets::Multiple>, BuilderError>;
6877
}
6978

7079
impl BuilderExt for Builder {
7180
fn build_with_wallet(
7281
mut self,
7382
wallet: &Wallet,
7483
scan_type: ScanType,
75-
) -> Result<LightClient<Idle>, BuilderError> {
84+
) -> Result<LightClient<Idle, crate::wallets::Single>, BuilderError> {
7685
let network = wallet.network();
7786
if self.network().ne(&network) {
7887
return Err(BuilderError::NetworkMismatch);
@@ -96,7 +105,7 @@ impl BuilderExt for Builder {
96105
event_rx,
97106
} = client;
98107
let indexed_graph = IndexedTxGraph::new(wallet.spk_index().clone());
99-
let update_subscriber = UpdateSubscriber::new(
108+
let update_subscriber = UpdateSubscriber::<crate::wallets::Single>::new(
100109
requester.clone(),
101110
scan_type,
102111
event_rx,
@@ -114,6 +123,72 @@ impl BuilderExt for Builder {
114123
);
115124
Ok(client)
116125
}
126+
127+
fn build_with_wallets(
128+
mut self,
129+
wallets: Vec<(&Wallet, ScanType)>,
130+
) -> Result<LightClient<Idle, crate::wallets::Multiple>, BuilderError> {
131+
let network = wallets
132+
.first()
133+
.ok_or(BuilderError::EmptyIterator)?
134+
.0
135+
.network();
136+
if self.network().ne(&network) {
137+
return Err(BuilderError::NetworkMismatch);
138+
}
139+
let cp_min = wallets
140+
.iter()
141+
.map(|(wallet, scan_type)| match scan_type {
142+
ScanType::Sync => walk_back_max_reorg(wallet.latest_checkpoint()),
143+
ScanType::Recovery {
144+
used_script_index: _,
145+
checkpoint,
146+
} => *checkpoint,
147+
})
148+
.min()
149+
.ok_or(BuilderError::EmptyIterator)?;
150+
self = self.chain_state(ChainState::Checkpoint(cp_min));
151+
let (node, client) = self.build();
152+
let bip157::Client {
153+
requester,
154+
info_rx,
155+
warn_rx,
156+
event_rx,
157+
} = client;
158+
let wallet_iter = wallets
159+
.into_iter()
160+
.map(|(wallet, scan_type)| {
161+
(
162+
wallet
163+
.public_descriptor(KeychainKind::External)
164+
.descriptor_id(),
165+
scan_type,
166+
wallet.latest_checkpoint(),
167+
IndexedTxGraph::new(wallet.spk_index().clone()),
168+
)
169+
})
170+
.collect::<Vec<(
171+
DescriptorId,
172+
ScanType,
173+
CheckPoint,
174+
IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>,
175+
)>>();
176+
let update_subscriber = UpdateSubscriber::<crate::wallets::Multiple>::new_multiple(
177+
requester.clone(),
178+
event_rx,
179+
wallet_iter.into_iter(),
180+
);
181+
let client = LightClient::new(
182+
requester,
183+
LoggingSubscribers {
184+
info_subscriber: info_rx,
185+
warning_subscriber: warn_rx,
186+
},
187+
update_subscriber,
188+
node,
189+
);
190+
Ok(client)
191+
}
117192
}
118193

119194
/// Walk back 7 blocks in case the last sync was an orphan block.
@@ -134,6 +209,8 @@ fn walk_back_max_reorg(checkpoint: CheckPoint) -> HeaderCheckpoint {
134209
pub enum BuilderError {
135210
/// The wallet network and node network do not match.
136211
NetworkMismatch,
212+
/// The wallet iterator is empty.
213+
EmptyIterator,
137214
}
138215

139216
impl Display for BuilderError {
@@ -142,6 +219,7 @@ impl Display for BuilderError {
142219
BuilderError::NetworkMismatch => {
143220
write!(f, "wallet network and node network do not match")
144221
}
222+
BuilderError::EmptyIterator => write!(f, "empty wallet iterator."),
145223
}
146224
}
147225
}

0 commit comments

Comments
 (0)