Skip to content

Commit 82a2423

Browse files
committed
Merge #1614: feat(rpc): introduce FilterIter
a6364e2 docs(rpc): add README and print ext address for FilterIter example (Steve Myers) 8fc03ee feat(rpc): introduce `FilterIter` (valued mammal) Pull request description: The PR adds a `bip158` module to the `bdk_bitcoind_rpc` crate along with a new type `FilterIter` that can be used for retrieving blocks from a full node which contain transactions relevant to a list of script pubkeys. ### Notes to the reviewers ### Changelog notice `bdk_bitcoind_rpc`: Added `bip158` module as a means of updating `bdk_chain` structures ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature ACKs for top commit: notmandatory: tACK a6364e2 Tree-SHA512: 430dd3d978d45ca8654ea2a8cc066717e8ba8b552734a0202cc8cdc6ca090683069c13925ab9b906f5a7783b2f1985d3c49ddfeb826525a01815b8c804aa1c3a
2 parents ab4df29 + a6364e2 commit 82a2423

File tree

6 files changed

+573
-1
lines changed

6 files changed

+573
-1
lines changed

crates/bitcoind_rpc/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ bitcoincore-rpc = { version = "0.19.0" }
2121
bdk_core = { path = "../core", version = "0.4.1", default-features = false }
2222

2323
[dev-dependencies]
24+
bdk_bitcoind_rpc = { path = "." }
2425
bdk_testenv = { path = "../testenv" }
2526
bdk_chain = { path = "../chain" }
2627

2728
[features]
2829
default = ["std"]
2930
std = ["bitcoin/std", "bdk_core/std"]
3031
serde = ["bitcoin/serde", "bdk_core/serde"]
32+
33+
[[example]]
34+
name = "filter_iter"
35+
required-features = ["std"]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Example bitcoind RPC sync
2+
3+
### Simple Signet Test with FilterIter
4+
5+
1. Start local signet bitcoind. (~8 GB space required)
6+
```
7+
mkdir -p /tmp/signet/bitcoind
8+
bitcoind -signet -server -fallbackfee=0.0002 -blockfilterindex -datadir=/tmp/signet/bitcoind -daemon
9+
tail -f /tmp/signet/bitcoind/signet/debug.log
10+
```
11+
Watch debug.log and wait for bitcoind to finish syncing.
12+
13+
2. Set bitcoind env variables.
14+
```
15+
export RPC_URL=127.0.0.1:38332
16+
export RPC_COOKIE=/tmp/signet/bitcoind/signet/.cookie
17+
```
18+
3. Run `filter_iter` example.
19+
```
20+
cargo run -p bdk_bitcoind_rpc --example filter_iter
21+
```
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#![allow(clippy::print_stdout)]
2+
use std::time::Instant;
3+
4+
use anyhow::Context;
5+
use bdk_bitcoind_rpc::bip158::{Event, EventInner, FilterIter};
6+
use bdk_chain::bitcoin::{constants::genesis_block, secp256k1::Secp256k1, Network};
7+
use bdk_chain::indexer::keychain_txout::KeychainTxOutIndex;
8+
use bdk_chain::local_chain::LocalChain;
9+
use bdk_chain::miniscript::Descriptor;
10+
use bdk_chain::{BlockId, ConfirmationBlockTime, IndexedTxGraph, SpkIterator};
11+
use bdk_testenv::anyhow;
12+
use bitcoin::Address;
13+
14+
// This example shows how BDK chain and tx-graph structures are updated using compact
15+
// filters syncing. Assumes a connection can be made to a bitcoin node via environment
16+
// variables `RPC_URL` and `RPC_COOKIE`.
17+
18+
// Usage: `cargo run -p bdk_bitcoind_rpc --example filter_iter`
19+
20+
const EXTERNAL: &str = "tr([7d94197e]tprv8ZgxMBicQKsPe1chHGzaa84k1inY2nAXUL8iPSyWESPrEst4E5oCFXhPATqj5fvw34LDknJz7rtXyEC4fKoXryUdc9q87pTTzfQyv61cKdE/86'/1'/0'/0/*)#uswl2jj7";
21+
const INTERNAL: &str = "tr([7d94197e]tprv8ZgxMBicQKsPe1chHGzaa84k1inY2nAXUL8iPSyWESPrEst4E5oCFXhPATqj5fvw34LDknJz7rtXyEC4fKoXryUdc9q87pTTzfQyv61cKdE/86'/1'/0'/1/*)#dyt7h8zx";
22+
const SPK_COUNT: u32 = 25;
23+
const NETWORK: Network = Network::Signet;
24+
25+
const START_HEIGHT: u32 = 170_000;
26+
const START_HASH: &str = "00000041c812a89f084f633e4cf47e819a2f6b1c0a15162355a930410522c99d";
27+
28+
fn main() -> anyhow::Result<()> {
29+
// Setup receiving chain and graph structures.
30+
let secp = Secp256k1::new();
31+
let (descriptor, _) = Descriptor::parse_descriptor(&secp, EXTERNAL)?;
32+
let (change_descriptor, _) = Descriptor::parse_descriptor(&secp, INTERNAL)?;
33+
let (mut chain, _) = LocalChain::from_genesis_hash(genesis_block(NETWORK).block_hash());
34+
let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<&str>>::new({
35+
let mut index = KeychainTxOutIndex::default();
36+
index.insert_descriptor("external", descriptor.clone())?;
37+
index.insert_descriptor("internal", change_descriptor.clone())?;
38+
index
39+
});
40+
41+
// Assume a minimum birthday height
42+
let block = BlockId {
43+
height: START_HEIGHT,
44+
hash: START_HASH.parse()?,
45+
};
46+
let _ = chain.insert_block(block)?;
47+
48+
// Configure RPC client
49+
let url = std::env::var("RPC_URL").context("must set RPC_URL")?;
50+
let cookie = std::env::var("RPC_COOKIE").context("must set RPC_COOKIE")?;
51+
let rpc_client =
52+
bitcoincore_rpc::Client::new(&url, bitcoincore_rpc::Auth::CookieFile(cookie.into()))?;
53+
54+
// Initialize block emitter
55+
let cp = chain.tip();
56+
let start_height = cp.height();
57+
let mut emitter = FilterIter::new_with_checkpoint(&rpc_client, cp);
58+
for (_, desc) in graph.index.keychains() {
59+
let spks = SpkIterator::new_with_range(desc, 0..SPK_COUNT).map(|(_, spk)| spk);
60+
emitter.add_spks(spks);
61+
}
62+
63+
let start = Instant::now();
64+
65+
// Sync
66+
if let Some(tip) = emitter.get_tip()? {
67+
let blocks_to_scan = tip.height - start_height;
68+
69+
for event in emitter.by_ref() {
70+
let event = event?;
71+
let curr = event.height();
72+
// apply relevant blocks
73+
if let Event::Block(EventInner { height, ref block }) = event {
74+
let _ = graph.apply_block_relevant(block, height);
75+
println!("Matched block {}", curr);
76+
}
77+
if curr % 1000 == 0 {
78+
let progress = (curr - start_height) as f32 / blocks_to_scan as f32;
79+
println!("[{:.2}%]", progress * 100.0);
80+
}
81+
}
82+
// update chain
83+
if let Some(cp) = emitter.chain_update() {
84+
let _ = chain.apply_update(cp)?;
85+
}
86+
}
87+
88+
println!("\ntook: {}s", start.elapsed().as_secs());
89+
println!("Local tip: {}", chain.tip().height());
90+
let unspent: Vec<_> = graph
91+
.graph()
92+
.filter_chain_unspents(
93+
&chain,
94+
chain.tip().block_id(),
95+
graph.index.outpoints().clone(),
96+
)
97+
.collect();
98+
if !unspent.is_empty() {
99+
println!("\nUnspent");
100+
for (index, utxo) in unspent {
101+
// (k, index) | value | outpoint |
102+
println!("{:?} | {} | {}", index, utxo.txout.value, utxo.outpoint);
103+
}
104+
}
105+
106+
let unused_spk = graph.index.reveal_next_spk("external").unwrap().0 .1;
107+
let unused_address = Address::from_script(&unused_spk, NETWORK)?;
108+
println!("Next external address: {}", unused_address);
109+
110+
Ok(())
111+
}

0 commit comments

Comments
 (0)