Skip to content

Commit 9613fd2

Browse files
committed
Merge #2000: FilterIter API redesign
6aa81fe test(bitcoind): Test 6-block reorg to `filter_iter_detects_reorgs` test (志宇) 4b295ee docs(bip158): Update `FilterIter` documentation (valued mammal) 2181c4b feat(rpc)!: `FilterIter` API redesign (valued mammal) Pull request description: Previously `FilterIter` did not detect or handle reorgs between `next` calls, meaning that if a reorg occurred, we might process blocks from a stale fork potentially resulting in an invalid wallet state. This PR aims to fix that by adding logic to explicitly check for and respond to a reorg on every call to `next`. ### Notes to the reviewers The old implementation required storing block IDs of scanned blocks before creating a checkpoint update, but because the interface was split across different methods, it introduced a timing risk between method calls which, when we consider the possibility of reorgs, made the implementation somewhat brittle. To address this, we make sure that 1) Finding the start block and 2) Updating the internal checkpoint are directly tied to the logic of `next`. Since the checkpoint in practice is derived from a clone of the local chain, this ensures that the checkpoint returned by `next` can always find a connection point with the receiver. Additionally we now emit a checkpoint at every height to ensure that any "must-include" heights are not missing. For example usage see `examples/filter_iter.rs` fixes #1848 ### Changelog notice Fixed - `FilterIter` now handles reorgs to ensure consistency of the header chain. Changed - `Event` is now a struct instead of enum Added - `FilterIter::new` constructor that takes as input a reference to the RPC client, checkpoint, and a list of SPKs. - `Error::ReorgDepthExceeded` variant - `Error::TryFromInt` variant Removed - `FilterIter::new_with_height` - `FilterIter::new_with_checkpoint` - `EventInner` type - `FilterIter::get_tip` - `FilterIter::chain_update` - `Error::NoScripts` variant ### Checklists #### All Submissions: * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature #### Bugfixes: * [x] This pull request breaks the existing API * [x] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: ValuedMammal: ACK 6aa81fe evanlinjin: ACK 6aa81fe oleonardolima: tACK 6aa81fe LagginTimes: ACK 6aa81fe Tree-SHA512: 4aebec0184f9cb0ba601811409cf782ab3ad1536bb2dd1f177b2263df5fe99739a675734aa00b28af1dd1bce77071ac18ac7f2c3e849cbd876a3639981af390a
2 parents 27ba19f + 6aa81fe commit 9613fd2

File tree

3 files changed

+284
-356
lines changed

3 files changed

+284
-356
lines changed
Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
1-
#![allow(clippy::print_stdout)]
1+
#![allow(clippy::print_stdout, clippy::print_stderr)]
22
use std::time::Instant;
33

44
use anyhow::Context;
5-
use bdk_bitcoind_rpc::bip158::{Event, EventInner, FilterIter};
5+
use bdk_bitcoind_rpc::bip158::{Event, FilterIter};
66
use bdk_chain::bitcoin::{constants::genesis_block, secp256k1::Secp256k1, Network};
77
use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
88
use bdk_chain::local_chain::LocalChain;
99
use bdk_chain::miniscript::Descriptor;
1010
use bdk_chain::{BlockId, ConfirmationBlockTime, IndexedTxGraph, SpkIterator};
1111
use bdk_testenv::anyhow;
12-
use bitcoin::Address;
1312

1413
// This example shows how BDK chain and tx-graph structures are updated using compact
1514
// filters syncing. Assumes a connection can be made to a bitcoin node via environment
1615
// variables `RPC_URL` and `RPC_COOKIE`.
1716

1817
// Usage: `cargo run -p bdk_bitcoind_rpc --example filter_iter`
1918

20-
const EXTERNAL: &str = "tr([7d94197e]tprv8ZgxMBicQKsPe1chHGzaa84k1inY2nAXUL8iPSyWESPrEst4E5oCFXhPATqj5fvw34LDknJz7rtXyEC4fKoXryUdc9q87pTTzfQyv61cKdE/86'/1'/0'/0/*)#uswl2jj7";
21-
const INTERNAL: &str = "tr([7d94197e]tprv8ZgxMBicQKsPe1chHGzaa84k1inY2nAXUL8iPSyWESPrEst4E5oCFXhPATqj5fvw34LDknJz7rtXyEC4fKoXryUdc9q87pTTzfQyv61cKdE/86'/1'/0'/1/*)#dyt7h8zx";
19+
const EXTERNAL: &str = "tr([83737d5e/86'/1'/0']tpubDDR5GgtoxS8fJyjjvdahN4VzV5DV6jtbcyvVXhEKq2XtpxjxBXmxH3r8QrNbQqHg4bJM1EGkxi7Pjfkgnui9jQWqS7kxHvX6rhUeriLDKxz/0/*)";
20+
const INTERNAL: &str = "tr([83737d5e/86'/1'/0']tpubDDR5GgtoxS8fJyjjvdahN4VzV5DV6jtbcyvVXhEKq2XtpxjxBXmxH3r8QrNbQqHg4bJM1EGkxi7Pjfkgnui9jQWqS7kxHvX6rhUeriLDKxz/1/*)";
2221
const SPK_COUNT: u32 = 25;
2322
const NETWORK: Network = Network::Signet;
2423

25-
const START_HEIGHT: u32 = 170_000;
26-
const START_HASH: &str = "00000041c812a89f084f633e4cf47e819a2f6b1c0a15162355a930410522c99d";
24+
const START_HEIGHT: u32 = 205_000;
25+
const START_HASH: &str = "0000002bd0f82f8c0c0f1e19128f84c938763641dba85c44bdb6aed1678d16cb";
2726

2827
fn main() -> anyhow::Result<()> {
2928
// Setup receiving chain and graph structures.
@@ -52,37 +51,22 @@ fn main() -> anyhow::Result<()> {
5251
let rpc_client =
5352
bitcoincore_rpc::Client::new(&url, bitcoincore_rpc::Auth::CookieFile(cookie.into()))?;
5453

55-
// Initialize block emitter
56-
let cp = chain.tip();
57-
let start_height = cp.height();
58-
let mut emitter = FilterIter::new_with_checkpoint(&rpc_client, cp);
54+
// Initialize `FilterIter`
55+
let mut spks = vec![];
5956
for (_, desc) in graph.index.keychains() {
60-
let spks = SpkIterator::new_with_range(desc, 0..SPK_COUNT).map(|(_, spk)| spk);
61-
emitter.add_spks(spks);
57+
spks.extend(SpkIterator::new_with_range(desc, 0..SPK_COUNT).map(|(_, s)| s));
6258
}
59+
let iter = FilterIter::new(&rpc_client, chain.tip(), spks);
6360

6461
let start = Instant::now();
6562

66-
// Sync
67-
if let Some(tip) = emitter.get_tip()? {
68-
let blocks_to_scan = tip.height - start_height;
69-
70-
for event in emitter.by_ref() {
71-
let event = event?;
72-
let curr = event.height();
73-
// apply relevant blocks
74-
if let Event::Block(EventInner { height, ref block }) = event {
75-
let _ = graph.apply_block_relevant(block, height);
76-
println!("Matched block {curr}");
77-
}
78-
if curr % 1000 == 0 {
79-
let progress = (curr - start_height) as f32 / blocks_to_scan as f32;
80-
println!("[{:.2}%]", progress * 100.0);
81-
}
82-
}
83-
// update chain
84-
if let Some(cp) = emitter.chain_update() {
85-
let _ = chain.apply_update(cp)?;
63+
for res in iter {
64+
let Event { cp, block } = res?;
65+
let height = cp.height();
66+
let _ = chain.apply_update(cp)?;
67+
if let Some(block) = block {
68+
let _ = graph.apply_block_relevant(&block, height);
69+
println!("Matched block {height}");
8670
}
8771
}
8872

@@ -105,9 +89,18 @@ fn main() -> anyhow::Result<()> {
10589
}
10690
}
10791

108-
let unused_spk = graph.index.reveal_next_spk("external").unwrap().0 .1;
109-
let unused_address = Address::from_script(&unused_spk, NETWORK)?;
110-
println!("Next external address: {unused_address}");
92+
for canon_tx in graph.graph().list_canonical_txs(
93+
&chain,
94+
chain.tip().block_id(),
95+
bdk_chain::CanonicalizationParams::default(),
96+
) {
97+
if !canon_tx.chain_position.is_confirmed() {
98+
eprintln!(
99+
"ERROR: canonical tx should be confirmed {}",
100+
canon_tx.tx_node.txid
101+
);
102+
}
103+
}
111104

112105
Ok(())
113106
}

0 commit comments

Comments
 (0)