Skip to content

Commit b401a2d

Browse files
committed
feat(wallet): add WalletEvent and Wallet::apply_update_events
WalletEvent is a enum of user facing events that are generated when a sync update is applied to a wallet using the Wallet::apply_update_events function.
1 parent 731efaa commit b401a2d

File tree

4 files changed

+593
-65
lines changed

4 files changed

+593
-65
lines changed

src/test_utils.rs

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use alloc::string::ToString;
44
use alloc::sync::Arc;
55
use core::str::FromStr;
66

7-
use bdk_chain::{BlockId, ConfirmationBlockTime, TxUpdate};
7+
use bdk_chain::{BlockId, CheckPoint, ConfirmationBlockTime, TxUpdate};
88
use bitcoin::{
99
absolute, hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint,
1010
Transaction, TxIn, TxOut, Txid,
@@ -22,13 +22,42 @@ pub fn get_funded_wallet(descriptor: &str, change_descriptor: &str) -> (Wallet,
2222
}
2323

2424
fn new_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wallet, Txid) {
25+
let (mut wallet, txid, update) = new_wallet_and_funding_update(descriptor, change_descriptor);
26+
wallet.apply_update(update).unwrap();
27+
(wallet, txid)
28+
}
29+
30+
/// Return a fake wallet that appears to be funded for testing.
31+
///
32+
/// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
33+
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
34+
/// sats are the transaction fee.
35+
pub fn get_funded_wallet_single(descriptor: &str) -> (Wallet, Txid) {
36+
new_funded_wallet(descriptor, None)
37+
}
38+
39+
/// Get funded segwit wallet
40+
pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) {
41+
let (desc, change_desc) = get_test_wpkh_and_change_desc();
42+
get_funded_wallet(desc, change_desc)
43+
}
44+
45+
/// Get unfunded wallet and wallet update that funds it
46+
///
47+
/// The funding update contains a tx with a 76_000 sats input and two outputs, one spending
48+
/// 25_000 to a foreign address and one returning 50_000 back to the wallet as
49+
/// change. The remaining 1000 sats are the transaction fee.
50+
pub fn new_wallet_and_funding_update(
51+
descriptor: &str,
52+
change_descriptor: Option<&str>,
53+
) -> (Wallet, Txid, Update) {
2554
let params = if let Some(change_desc) = change_descriptor {
2655
Wallet::create(descriptor.to_string(), change_desc.to_string())
2756
} else {
2857
Wallet::create_single(descriptor.to_string())
2958
};
3059

31-
let mut wallet = params
60+
let wallet = params
3261
.network(Network::Regtest)
3362
.create_wallet_no_persist()
3463
.expect("descriptors must be valid");
@@ -39,6 +68,8 @@ fn new_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wall
3968
.require_network(Network::Regtest)
4069
.unwrap();
4170

71+
let mut update = Update::default();
72+
4273
let tx0 = Transaction {
4374
output: vec![TxOut {
4475
value: Amount::from_sat(76_000),
@@ -67,71 +98,37 @@ fn new_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wall
6798
],
6899
..new_tx(0)
69100
};
101+
let txid1 = tx1.compute_txid();
70102

71-
insert_checkpoint(
72-
&mut wallet,
73-
BlockId {
74-
height: 42,
75-
hash: BlockHash::all_zeros(),
76-
},
77-
);
78-
insert_checkpoint(
79-
&mut wallet,
80-
BlockId {
81-
height: 1_000,
82-
hash: BlockHash::all_zeros(),
83-
},
84-
);
85-
insert_checkpoint(
86-
&mut wallet,
87-
BlockId {
88-
height: 2_000,
89-
hash: BlockHash::all_zeros(),
90-
},
91-
);
92-
93-
insert_tx(&mut wallet, tx0.clone());
94-
insert_anchor(
95-
&mut wallet,
96-
tx0.compute_txid(),
97-
ConfirmationBlockTime {
98-
block_id: BlockId {
99-
height: 1_000,
100-
hash: BlockHash::all_zeros(),
101-
},
102-
confirmation_time: 100,
103-
},
104-
);
105-
106-
insert_tx(&mut wallet, tx1.clone());
107-
insert_anchor(
108-
&mut wallet,
109-
tx1.compute_txid(),
110-
ConfirmationBlockTime {
111-
block_id: BlockId {
112-
height: 2_000,
113-
hash: BlockHash::all_zeros(),
114-
},
115-
confirmation_time: 200,
116-
},
117-
);
118-
119-
(wallet, tx1.compute_txid())
120-
}
121-
122-
/// Return a fake wallet that appears to be funded for testing.
123-
///
124-
/// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
125-
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
126-
/// sats are the transaction fee.
127-
pub fn get_funded_wallet_single(descriptor: &str) -> (Wallet, Txid) {
128-
new_funded_wallet(descriptor, None)
129-
}
103+
let b0 = BlockId {
104+
height: 0,
105+
hash: BlockHash::from_slice(wallet.network().chain_hash().as_bytes()).unwrap(),
106+
};
107+
let b1 = BlockId {
108+
height: 42,
109+
hash: BlockHash::all_zeros(),
110+
};
111+
let b2 = BlockId {
112+
height: 1000,
113+
hash: BlockHash::all_zeros(),
114+
};
115+
let a2 = ConfirmationBlockTime {
116+
block_id: b2,
117+
confirmation_time: 100,
118+
};
119+
let b3 = BlockId {
120+
height: 2000,
121+
hash: BlockHash::all_zeros(),
122+
};
123+
let a3 = ConfirmationBlockTime {
124+
block_id: b3,
125+
confirmation_time: 200,
126+
};
127+
update.chain = CheckPoint::from_block_ids([b0, b1, b2, b3]).ok();
128+
update.tx_update.anchors = [(a2, tx0.compute_txid()), (a3, tx1.compute_txid())].into();
129+
update.tx_update.txs = [Arc::new(tx0), Arc::new(tx1)].into();
130130

131-
/// Get funded segwit wallet
132-
pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) {
133-
let (desc, change_desc) = get_test_wpkh_and_change_desc();
134-
get_funded_wallet(desc, change_desc)
131+
(wallet, txid1, update)
135132
}
136133

137134
/// `pkh` single key descriptor

src/wallet/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use rand_core::RngCore;
5252
mod changeset;
5353
pub mod coin_selection;
5454
pub mod error;
55+
pub mod event;
5556
pub mod export;
5657
mod params;
5758
mod persisted;
@@ -76,6 +77,7 @@ use crate::wallet::{
7677
};
7778

7879
// re-exports
80+
use crate::event::{wallet_events, WalletEvent};
7981
pub use bdk_chain::Balance;
8082
pub use changeset::ChangeSet;
8183
pub use params::*;
@@ -2374,6 +2376,54 @@ impl Wallet {
23742376
Ok(())
23752377
}
23762378

2379+
/// Applies an update to the wallet, stages the changes, and returns events.
2380+
///
2381+
/// Usually you create an `update` by interacting with some blockchain data source and inserting
2382+
/// transactions related to your wallet into it. Staged changes are NOT persisted.
2383+
///
2384+
/// After applying updates you should process the events in your app before persisting the
2385+
/// staged wallet changes. For an example of how to persist staged wallet changes see
2386+
/// [`Wallet::reveal_next_address`].
2387+
pub fn apply_update_events(
2388+
&mut self,
2389+
update: impl Into<Update>,
2390+
) -> Result<Vec<WalletEvent>, CannotConnectError> {
2391+
// snapshot of chain tip and transactions before update
2392+
let chain_tip1 = self.chain.tip().block_id();
2393+
let wallet_txs1 = self
2394+
.transactions()
2395+
.map(|wtx| {
2396+
(
2397+
wtx.tx_node.txid,
2398+
(wtx.tx_node.tx.clone(), wtx.chain_position),
2399+
)
2400+
})
2401+
.collect::<BTreeMap<Txid, (Arc<Transaction>, ChainPosition<ConfirmationBlockTime>)>>();
2402+
2403+
// apply update
2404+
self.apply_update(update)?;
2405+
2406+
// chain tip and transactions after update
2407+
let chain_tip2 = self.chain.tip().block_id();
2408+
let wallet_txs2 = self
2409+
.transactions()
2410+
.map(|wtx| {
2411+
(
2412+
wtx.tx_node.txid,
2413+
(wtx.tx_node.tx.clone(), wtx.chain_position),
2414+
)
2415+
})
2416+
.collect::<BTreeMap<Txid, (Arc<Transaction>, ChainPosition<ConfirmationBlockTime>)>>();
2417+
2418+
Ok(wallet_events(
2419+
self,
2420+
chain_tip1,
2421+
chain_tip2,
2422+
wallet_txs1,
2423+
wallet_txs2,
2424+
))
2425+
}
2426+
23772427
/// Get a reference of the staged [`ChangeSet`] that is yet to be committed (if any).
23782428
pub fn staged(&self) -> Option<&ChangeSet> {
23792429
if self.stage.is_empty() {

0 commit comments

Comments
 (0)