Skip to content

Commit ca63c97

Browse files
committed
[bitcoind_rpc] Initial work on BitcoindRpcIter
1 parent af75d3f commit ca63c97

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"crates/file_store",
66
"crates/electrum",
77
"crates/esplora",
8+
"crates/bitcoind_rpc",
89
"example-crates/example_cli",
910
"example-crates/example_electrum",
1011
"example-crates/wallet_electrum",

crates/bitcoind_rpc/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "bitcoind_rpc"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
bdk_chain = { path = "../chain", version = "0.4.0", features = ["serde", "miniscript"] }
10+
bitcoincore-rpc = { version = "0.16" }
11+
anyhow = { version = "1" }

crates/bitcoind_rpc/src/lib.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use std::collections::HashSet;
2+
3+
use bdk_chain::{
4+
bitcoin::{Transaction, Txid},
5+
local_chain::CheckPoint,
6+
BlockId,
7+
};
8+
use bitcoincore_rpc::{bitcoincore_rpc_json::GetBlockResult, Client, RpcApi};
9+
10+
#[derive(Debug, Clone)]
11+
pub enum BitcoindRpcItem {
12+
Block {
13+
cp: CheckPoint,
14+
info: Box<GetBlockResult>,
15+
},
16+
Mempool {
17+
cp: CheckPoint,
18+
txs: Vec<(Transaction, u64)>,
19+
},
20+
}
21+
22+
pub struct BitcoindRpcIter<'a> {
23+
client: &'a Client,
24+
fallback_height: u32,
25+
26+
last_cp: Option<CheckPoint>,
27+
last_info: Option<GetBlockResult>,
28+
29+
seen_txids: HashSet<Txid>,
30+
}
31+
32+
impl<'a> Iterator for BitcoindRpcIter<'a> {
33+
type Item = Result<BitcoindRpcItem, bitcoincore_rpc::Error>;
34+
35+
fn next(&mut self) -> Option<Self::Item> {
36+
self.next_emission().transpose()
37+
}
38+
}
39+
40+
impl<'a> BitcoindRpcIter<'a> {
41+
pub fn new(client: &'a Client, fallback_height: u32, last_cp: Option<CheckPoint>) -> Self {
42+
Self {
43+
client,
44+
fallback_height,
45+
last_cp,
46+
last_info: None,
47+
seen_txids: HashSet::new(),
48+
}
49+
}
50+
51+
fn next_emission(&mut self) -> Result<Option<BitcoindRpcItem>, bitcoincore_rpc::Error> {
52+
let client = self.client;
53+
54+
'main_loop: loop {
55+
match (&mut self.last_cp, &mut self.last_info) {
56+
(last_cp @ None, last_info @ None) => {
57+
// get first item at fallback_height
58+
let info = client
59+
.get_block_info(&client.get_block_hash(self.fallback_height as _)?)?;
60+
let cp = CheckPoint::new(BlockId {
61+
height: info.height as _,
62+
hash: info.hash,
63+
});
64+
*last_info = Some(info.clone());
65+
*last_cp = Some(cp.clone());
66+
return Ok(Some(BitcoindRpcItem::Block {
67+
cp,
68+
info: Box::new(info),
69+
}));
70+
}
71+
(last_cp @ Some(_), last_info @ None) => {
72+
'cp_loop: for cp in last_cp.clone().iter().flat_map(CheckPoint::iter) {
73+
let cp_block = cp.block_id();
74+
75+
let info = client.get_block_info(&cp_block.hash)?;
76+
if info.confirmations < 0 {
77+
// block is not in the main chain
78+
continue 'cp_loop;
79+
}
80+
// agreement
81+
// next loop
82+
*last_cp = Some(cp);
83+
*last_info = Some(info);
84+
}
85+
86+
// no point of agreement found
87+
// next loop will emit block @ fallback height
88+
*last_cp = None;
89+
}
90+
(Some(last_cp), last_info @ Some(_)) => {
91+
// find next block
92+
match last_info.as_ref().unwrap().nextblockhash {
93+
Some(next_hash) => {
94+
let info = self.client.get_block_info(&next_hash)?;
95+
96+
if info.confirmations < 0 {
97+
*last_info = None;
98+
continue 'main_loop;
99+
}
100+
101+
let cp = CheckPoint::new_with_prev(
102+
BlockId {
103+
height: info.height as _,
104+
hash: info.hash,
105+
},
106+
Some(last_cp.clone()),
107+
)
108+
.expect("must create valid checkpoint");
109+
110+
*last_cp = cp.clone();
111+
*last_info = Some(info.clone());
112+
113+
return Ok(Some(BitcoindRpcItem::Block {
114+
cp,
115+
info: Box::new(info),
116+
}));
117+
}
118+
None => {
119+
// emit from mempool!
120+
let mempool_txs = client
121+
.get_raw_mempool()?
122+
.into_iter()
123+
.filter(|&txid| self.seen_txids.insert(txid))
124+
.map(
125+
|txid| -> Result<(Transaction, u64), bitcoincore_rpc::Error> {
126+
let first_seen = client
127+
.get_mempool_entry(&txid)
128+
.map(|entry| entry.time)?;
129+
let tx = client.get_raw_transaction(&txid, None)?;
130+
Ok((tx, first_seen))
131+
},
132+
)
133+
.collect::<Result<Vec<_>, _>>()?;
134+
135+
// remove last info...
136+
*last_info = None;
137+
138+
return Ok(Some(BitcoindRpcItem::Mempool {
139+
txs: mempool_txs,
140+
cp: last_cp.clone(),
141+
}));
142+
}
143+
}
144+
}
145+
(None, Some(_)) => unreachable!(),
146+
}
147+
}
148+
}
149+
}
150+
151+
pub trait BitcoindRpcErrorExt {
152+
fn is_not_found_error(&self) -> bool;
153+
}
154+
155+
impl BitcoindRpcErrorExt for bitcoincore_rpc::Error {
156+
fn is_not_found_error(&self) -> bool {
157+
if let bitcoincore_rpc::Error::JsonRpc(bitcoincore_rpc::jsonrpc::Error::Rpc(rpc_err)) = self
158+
{
159+
rpc_err.code == -5
160+
} else {
161+
false
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)