Skip to content

Commit 2188b6c

Browse files
committed
[bitcoind_rpc] Introduce prune_and_apply_update for IndexedTxGraph
`prune_and_apply_update` first scans all txs contained in `update` through the index, then filters out txs using `I::is_tx_relevant` before applying the update. This is useful for block-by-block syncing. `Wallet::apply_update` now has a second input; `prune: bool`. If `prune` is set, irrelevant transactions of `update` will not be included.
1 parent ca63c97 commit 2188b6c

File tree

6 files changed

+124
-15
lines changed

6 files changed

+124
-15
lines changed

crates/bdk/src/wallet/mod.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,14 +1694,16 @@ impl<D> Wallet<D> {
16941694
}
16951695

16961696
/// Applies an update to the wallet and stages the changes (but does not [`commit`] them).
1697+
/// Returns whether the `update` resulted in any changes.
16971698
///
1698-
/// This returns whether the `update` resulted in any changes.
1699+
/// If `prune` is set, irrelevant transactions are pruned. Relevant transactions change the UTXO
1700+
/// set of tracked script pubkeys (script pubkeys derived from tracked descriptors).
16991701
///
17001702
/// Usually you create an `update` by interacting with some blockchain data source and inserting
17011703
/// transactions related to your wallet into it.
17021704
///
17031705
/// [`commit`]: Self::commit
1704-
pub fn apply_update(&mut self, update: Update) -> Result<bool, CannotConnectError>
1706+
pub fn apply_update(&mut self, update: Update, prune: bool) -> Result<bool, CannotConnectError>
17051707
where
17061708
D: PersistBackend<ChangeSet>,
17071709
{
@@ -1711,7 +1713,14 @@ impl<D> Wallet<D> {
17111713
.index
17121714
.reveal_to_target_multi(&update.keychain);
17131715
changeset.append(ChangeSet::from(IndexedAdditions::from(index_additions)));
1714-
changeset.append(self.indexed_graph.apply_update(update.graph).into());
1716+
changeset.append(
1717+
if prune {
1718+
self.indexed_graph.prune_and_apply_update(update.graph)
1719+
} else {
1720+
self.indexed_graph.apply_update(update.graph)
1721+
}
1722+
.into(),
1723+
);
17151724

17161725
let changed = !changeset.is_empty();
17171726
self.persist.stage(changeset);

crates/bitcoind_rpc/src/lib.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::collections::HashSet;
22

33
use bdk_chain::{
4-
bitcoin::{Transaction, Txid},
4+
bitcoin::{Block, Transaction, Txid},
5+
keychain::LocalUpdate,
56
local_chain::CheckPoint,
6-
BlockId,
7+
BlockId, ConfirmationHeightAnchor, ConfirmationTimeAnchor, TxGraph,
78
};
89
use bitcoincore_rpc::{bitcoincore_rpc_json::GetBlockResult, Client, RpcApi};
910

@@ -12,13 +13,78 @@ pub enum BitcoindRpcItem {
1213
Block {
1314
cp: CheckPoint,
1415
info: Box<GetBlockResult>,
16+
block: Box<Block>,
1517
},
1618
Mempool {
1719
cp: CheckPoint,
1820
txs: Vec<(Transaction, u64)>,
1921
},
2022
}
2123

24+
pub fn confirmation_height_anchor(
25+
info: &GetBlockResult,
26+
_txid: Txid,
27+
_tx_pos: usize,
28+
) -> ConfirmationHeightAnchor {
29+
ConfirmationHeightAnchor {
30+
anchor_block: BlockId {
31+
height: info.height as _,
32+
hash: info.hash,
33+
},
34+
confirmation_height: info.height as _,
35+
}
36+
}
37+
38+
pub fn confirmation_time_anchor(
39+
info: &GetBlockResult,
40+
_txid: Txid,
41+
_tx_pos: usize,
42+
) -> ConfirmationTimeAnchor {
43+
ConfirmationTimeAnchor {
44+
anchor_block: BlockId {
45+
height: info.height as _,
46+
hash: info.hash,
47+
},
48+
confirmation_height: info.height as _,
49+
confirmation_time: info.time as _,
50+
}
51+
}
52+
53+
impl BitcoindRpcItem {
54+
pub fn into_update<K, A, F>(self, anchor: F) -> LocalUpdate<K, A>
55+
where
56+
A: Clone + Ord + PartialOrd,
57+
F: Fn(&GetBlockResult, Txid, usize) -> A,
58+
{
59+
match self {
60+
BitcoindRpcItem::Block { cp, info, block } => LocalUpdate {
61+
graph: {
62+
let mut g = TxGraph::<A>::new(block.txdata);
63+
for (tx_pos, &txid) in info.tx.iter().enumerate() {
64+
let _ = g.insert_anchor(txid, anchor(&info, txid, tx_pos));
65+
}
66+
g
67+
},
68+
..LocalUpdate::new(cp)
69+
},
70+
BitcoindRpcItem::Mempool { cp, txs } => LocalUpdate {
71+
graph: {
72+
let mut last_seens = Vec::<(Txid, u64)>::with_capacity(txs.len());
73+
let mut g = TxGraph::<A>::new(txs.into_iter().map(|(tx, last_seen)| {
74+
last_seens.push((tx.txid(), last_seen));
75+
tx
76+
}));
77+
for (txid, seen_at) in last_seens {
78+
let _ = g.insert_seen_at(txid, seen_at);
79+
}
80+
g
81+
},
82+
..LocalUpdate::new(cp)
83+
},
84+
}
85+
}
86+
}
87+
2288
pub struct BitcoindRpcIter<'a> {
2389
client: &'a Client,
2490
fallback_height: u32,
@@ -57,6 +123,7 @@ impl<'a> BitcoindRpcIter<'a> {
57123
// get first item at fallback_height
58124
let info = client
59125
.get_block_info(&client.get_block_hash(self.fallback_height as _)?)?;
126+
let block = self.client.get_block(&info.hash)?;
60127
let cp = CheckPoint::new(BlockId {
61128
height: info.height as _,
62129
hash: info.hash,
@@ -66,6 +133,7 @@ impl<'a> BitcoindRpcIter<'a> {
66133
return Ok(Some(BitcoindRpcItem::Block {
67134
cp,
68135
info: Box::new(info),
136+
block: Box::new(block),
69137
}));
70138
}
71139
(last_cp @ Some(_), last_info @ None) => {
@@ -98,6 +166,7 @@ impl<'a> BitcoindRpcIter<'a> {
98166
continue 'main_loop;
99167
}
100168

169+
let block = self.client.get_block(&info.hash)?;
101170
let cp = CheckPoint::new_with_prev(
102171
BlockId {
103172
height: info.height as _,
@@ -113,6 +182,7 @@ impl<'a> BitcoindRpcIter<'a> {
113182
return Ok(Some(BitcoindRpcItem::Block {
114183
cp,
115184
info: Box::new(info),
185+
block: Box::new(block),
116186
}));
117187
}
118188
None => {

crates/chain/src/indexed_tx_graph.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,38 @@ where
9090
}
9191
}
9292

93+
/// Apply `update`, but filters out irrelevant transactions.
94+
///
95+
/// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`.
96+
pub fn prune_and_apply_update(
97+
&mut self,
98+
update: TxGraph<A>,
99+
) -> IndexedAdditions<A, I::Additions> {
100+
let mut additions = IndexedAdditions::<A, I::Additions>::default();
101+
102+
// index all transactions first
103+
for tx_node in update.full_txs() {
104+
additions
105+
.index_additions
106+
.append(self.index.index_tx(&tx_node));
107+
}
108+
109+
let update = update
110+
.full_txs()
111+
.filter(|tx_node| self.index.is_tx_relevant(tx_node))
112+
.fold(TxGraph::default(), |mut g, tx_node| -> TxGraph<A> {
113+
let _ = g.insert_tx(tx_node.tx.clone());
114+
for anchor in tx_node.anchors {
115+
let _ = g.insert_anchor(tx_node.txid, anchor.clone());
116+
}
117+
let _ = g.insert_seen_at(tx_node.txid, tx_node.last_seen_unconfirmed);
118+
g
119+
});
120+
121+
additions.append(self.apply_update(update));
122+
additions
123+
}
124+
93125
/// Insert a floating `txout` of given `outpoint`.
94126
pub fn insert_txout(
95127
&mut self,
@@ -146,14 +178,12 @@ where
146178
// 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
147179
// returns true or not. (in a second loop).
148180
let mut additions = IndexedAdditions::<A, I::Additions>::default();
149-
let mut transactions = Vec::new();
150-
for (tx, anchors) in txs.into_iter() {
151-
additions.index_additions.append(self.index.index_tx(tx));
152-
transactions.push((tx, anchors));
153-
}
181+
let txs = txs
182+
.into_iter()
183+
.inspect(|(tx, _)| additions.index_additions.append(self.index.index_tx(tx)))
184+
.collect::<Vec<_>>();
154185
additions.append(
155-
transactions
156-
.into_iter()
186+
txs.into_iter()
157187
.filter_map(|(tx, anchors)| match self.index.is_tx_relevant(tx) {
158188
true => Some(self.insert_tx(tx, anchors, seen_at)),
159189
false => None,

example-crates/wallet_electrum/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
5959
let missing = electrum_update.missing_full_txs(wallet.as_ref());
6060
let update = electrum_update.finalize_as_confirmation_time(&client, None, missing)?;
6161

62-
wallet.apply_update(update)?;
62+
wallet.apply_update(update, false)?;
6363
wallet.commit()?;
6464

6565
let balance = wallet.get_balance();

example-crates/wallet_esplora/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
6161
PARALLEL_REQUESTS,
6262
)?;
6363
println!();
64-
wallet.apply_update(update)?;
64+
wallet.apply_update(update, false)?;
6565
wallet.commit()?;
6666

6767
let balance = wallet.get_balance();

example-crates/wallet_esplora_async/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
5757
.scan(prev_cp, keychain_spks, [], [], STOP_GAP, PARALLEL_REQUESTS)
5858
.await?;
5959
println!();
60-
wallet.apply_update(update)?;
60+
wallet.apply_update(update, false)?;
6161
wallet.commit()?;
6262

6363
let balance = wallet.get_balance();

0 commit comments

Comments
 (0)