Skip to content

Commit 32c40ac

Browse files
committed
feat(electrum)!: change signature of ElectrumExt
We remove `ElectrumUpdate` and return tuples instead for `ElectrumExt` methods. We introduce the `IncompleteTxGraph` structure to specifically hodl the incomplete `TxGraph`. This change is motivated by @LLFourn's comment: 794bf37#r1305432603
1 parent a28748c commit 32c40ac

File tree

4 files changed

+93
-103
lines changed

4 files changed

+93
-103
lines changed

crates/electrum/src/electrum_ext.rs

Lines changed: 70 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -14,86 +14,63 @@ use std::{
1414
/// We assume that a block of this depth and deeper cannot be reorged.
1515
const ASSUME_FINAL_DEPTH: u32 = 8;
1616

17-
/// Represents an update fetched from an Electrum server, but excludes full
18-
/// transactions.
17+
/// Represents a [`TxGraph`] update fetched from an Electrum server, but excludes full transactions.
1918
///
2019
/// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to
21-
/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to fetch
22-
/// the full transactions from Electrum and finalize the update.
23-
#[derive(Debug, Clone)]
24-
pub struct ElectrumUpdate<K, A> {
25-
/// Map of [`Txid`]s to associated [`Anchor`]s.
26-
pub graph_update: HashMap<Txid, BTreeSet<A>>,
27-
/// The latest chain tip, as seen by the Electrum server.
28-
pub new_tip: local_chain::CheckPoint,
29-
/// Last-used index update for [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex).
30-
pub keychain_update: BTreeMap<K, u32>,
31-
}
32-
33-
impl<K, A: Anchor> ElectrumUpdate<K, A> {
34-
fn new(new_tip: local_chain::CheckPoint) -> Self {
35-
Self {
36-
new_tip,
37-
graph_update: HashMap::new(),
38-
keychain_update: BTreeMap::new(),
39-
}
40-
}
20+
/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to
21+
/// fetch the full transactions from Electrum and finalize the update.
22+
#[derive(Debug, Default, Clone)]
23+
pub struct IncompleteTxGraph<A>(HashMap<Txid, BTreeSet<A>>);
4124

25+
impl<A: Anchor> IncompleteTxGraph<A> {
4226
/// Determine the full transactions that are missing from `graph`.
4327
///
44-
/// Refer to [`ElectrumUpdate`].
28+
/// Refer to [`IncompleteTxGraph`] for more.
4529
pub fn missing_full_txs<A2>(&self, graph: &TxGraph<A2>) -> Vec<Txid> {
46-
self.graph_update
30+
self.0
4731
.keys()
4832
.filter(move |&&txid| graph.as_ref().get_tx(txid).is_none())
4933
.cloned()
5034
.collect()
5135
}
5236

53-
/// Finalizes update with `missing` txids to fetch from `client`.
37+
/// Finalizes the [`TxGraph`] update by fetching `missing` txids from the `client`.
5438
///
55-
/// Refer to [`ElectrumUpdate`].
39+
/// Refer to [`IncompleteTxGraph`] for more.
5640
pub fn finalize(
5741
self,
5842
client: &Client,
5943
seen_at: Option<u64>,
6044
missing: Vec<Txid>,
61-
) -> Result<(TxGraph<A>, BTreeMap<K, u32>, local_chain::CheckPoint), Error> {
45+
) -> Result<TxGraph<A>, Error> {
6246
let new_txs = client.batch_transaction_get(&missing)?;
63-
let mut graph_update = TxGraph::<A>::new(new_txs);
64-
for (txid, anchors) in self.graph_update {
47+
let mut graph = TxGraph::<A>::new(new_txs);
48+
for (txid, anchors) in self.0 {
6549
if let Some(seen_at) = seen_at {
66-
let _ = graph_update.insert_seen_at(txid, seen_at);
50+
let _ = graph.insert_seen_at(txid, seen_at);
6751
}
6852
for anchor in anchors {
69-
let _ = graph_update.insert_anchor(txid, anchor);
53+
let _ = graph.insert_anchor(txid, anchor);
7054
}
7155
}
72-
Ok((graph_update, self.keychain_update, self.new_tip))
56+
Ok(graph)
7357
}
7458
}
7559

76-
impl<K> ElectrumUpdate<K, ConfirmationHeightAnchor> {
77-
/// Finalizes the [`ElectrumUpdate`] with `new_txs` and anchors of type
60+
impl IncompleteTxGraph<ConfirmationHeightAnchor> {
61+
/// Finalizes the [`IncompleteTxGraph`] with `new_txs` and anchors of type
7862
/// [`ConfirmationTimeAnchor`].
7963
///
8064
/// **Note:** The confirmation time might not be precisely correct if there has been a reorg.
8165
/// Electrum's API intends that we use the merkle proof API, we should change `bdk_electrum` to
8266
/// use it.
83-
pub fn finalize_as_confirmation_time(
67+
pub fn finalize_with_confirmation_time(
8468
self,
8569
client: &Client,
8670
seen_at: Option<u64>,
8771
missing: Vec<Txid>,
88-
) -> Result<
89-
(
90-
TxGraph<ConfirmationTimeAnchor>,
91-
BTreeMap<K, u32>,
92-
local_chain::CheckPoint,
93-
),
94-
Error,
95-
> {
96-
let (graph, keychain_update, update_tip) = self.finalize(client, seen_at, missing)?;
72+
) -> Result<TxGraph<ConfirmationTimeAnchor>, Error> {
73+
let graph = self.finalize(client, seen_at, missing)?;
9774

9875
let relevant_heights = {
9976
let mut visited_heights = HashSet::new();
@@ -117,7 +94,7 @@ impl<K> ElectrumUpdate<K, ConfirmationHeightAnchor> {
11794
.collect::<HashMap<u32, u64>>();
11895

11996
let graph_changeset = {
120-
let old_changeset = TxGraph::default().apply_update(graph.clone());
97+
let old_changeset = TxGraph::default().apply_update(graph);
12198
tx_graph::ChangeSet {
12299
txs: old_changeset.txs,
123100
txouts: old_changeset.txouts,
@@ -139,16 +116,16 @@ impl<K> ElectrumUpdate<K, ConfirmationHeightAnchor> {
139116
}
140117
};
141118

142-
let mut update = TxGraph::default();
143-
update.apply_changeset(graph_changeset);
144-
145-
Ok((update, keychain_update, update_tip))
119+
let mut new_graph = TxGraph::default();
120+
new_graph.apply_changeset(graph_changeset);
121+
Ok(new_graph)
146122
}
147123
}
148124

149125
/// Trait to extend [`Client`] functionality.
150126
pub trait ElectrumExt<A> {
151-
/// Scan the blockchain (via electrum) for the data specified and returns a [`ElectrumUpdate`].
127+
/// Scan the blockchain (via electrum) for the data specified and returns updates for
128+
/// [`bdk_chain`] data structures.
152129
///
153130
/// - `prev_tip`: the most recent blockchain tip present locally
154131
/// - `keychain_spks`: keychains that we want to scan transactions for
@@ -159,6 +136,7 @@ pub trait ElectrumExt<A> {
159136
/// The scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
160137
/// transactions. `batch_size` specifies the max number of script pubkeys to request for in a
161138
/// single batch request.
139+
#[allow(clippy::type_complexity)]
162140
fn scan<K: Ord + Clone>(
163141
&self,
164142
prev_tip: Option<CheckPoint>,
@@ -167,7 +145,7 @@ pub trait ElectrumExt<A> {
167145
outpoints: impl IntoIterator<Item = OutPoint>,
168146
stop_gap: usize,
169147
batch_size: usize,
170-
) -> Result<ElectrumUpdate<K, A>, Error>;
148+
) -> Result<(local_chain::Update, IncompleteTxGraph<A>, BTreeMap<K, u32>), Error>;
171149

172150
/// Convenience method to call [`scan`] without requiring a keychain.
173151
///
@@ -179,20 +157,22 @@ pub trait ElectrumExt<A> {
179157
txids: impl IntoIterator<Item = Txid>,
180158
outpoints: impl IntoIterator<Item = OutPoint>,
181159
batch_size: usize,
182-
) -> Result<ElectrumUpdate<(), A>, Error> {
160+
) -> Result<(local_chain::Update, IncompleteTxGraph<A>), Error> {
183161
let spk_iter = misc_spks
184162
.into_iter()
185163
.enumerate()
186164
.map(|(i, spk)| (i as u32, spk));
187165

188-
self.scan(
166+
let (chain, graph, _) = self.scan(
189167
prev_tip,
190168
[((), spk_iter)].into(),
191169
txids,
192170
outpoints,
193171
usize::MAX,
194172
batch_size,
195-
)
173+
)?;
174+
175+
Ok((chain, graph))
196176
}
197177
}
198178

@@ -205,7 +185,14 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
205185
outpoints: impl IntoIterator<Item = OutPoint>,
206186
stop_gap: usize,
207187
batch_size: usize,
208-
) -> Result<ElectrumUpdate<K, ConfirmationHeightAnchor>, Error> {
188+
) -> Result<
189+
(
190+
local_chain::Update,
191+
IncompleteTxGraph<ConfirmationHeightAnchor>,
192+
BTreeMap<K, u32>,
193+
),
194+
Error,
195+
> {
209196
let mut request_spks = keychain_spks
210197
.into_iter()
211198
.map(|(k, s)| (k, s.into_iter()))
@@ -217,9 +204,8 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
217204

218205
let update = loop {
219206
let (tip, _) = construct_update_tip(self, prev_tip.clone())?;
220-
let mut update = ElectrumUpdate::<K, ConfirmationHeightAnchor>::new(tip.clone());
221-
let cps = update
222-
.new_tip
207+
let mut graph_update = IncompleteTxGraph::<ConfirmationHeightAnchor>::default();
208+
let cps = tip
223209
.iter()
224210
.take(10)
225211
.map(|cp| (cp.height(), cp))
@@ -230,7 +216,7 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
230216
scanned_spks.append(&mut populate_with_spks(
231217
self,
232218
&cps,
233-
&mut update,
219+
&mut graph_update,
234220
&mut scanned_spks
235221
.iter()
236222
.map(|(i, (spk, _))| (i.clone(), spk.clone())),
@@ -243,7 +229,7 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
243229
populate_with_spks(
244230
self,
245231
&cps,
246-
&mut update,
232+
&mut graph_update,
247233
keychain_spks,
248234
stop_gap,
249235
batch_size,
@@ -254,18 +240,27 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
254240
}
255241
}
256242

257-
populate_with_txids(self, &cps, &mut update, &mut txids.iter().cloned())?;
243+
populate_with_txids(self, &cps, &mut graph_update, &mut txids.iter().cloned())?;
258244

259-
let _txs =
260-
populate_with_outpoints(self, &cps, &mut update, &mut outpoints.iter().cloned())?;
245+
let _txs = populate_with_outpoints(
246+
self,
247+
&cps,
248+
&mut graph_update,
249+
&mut outpoints.iter().cloned(),
250+
)?;
261251

262252
// check for reorgs during scan process
263253
let server_blockhash = self.block_header(tip.height() as usize)?.block_hash();
264254
if tip.hash() != server_blockhash {
265255
continue; // reorg
266256
}
267257

268-
update.keychain_update = request_spks
258+
let chain_update = local_chain::Update {
259+
tip,
260+
introduce_older_blocks: true,
261+
};
262+
263+
let keychain_update = request_spks
269264
.into_keys()
270265
.filter_map(|k| {
271266
scanned_spks
@@ -275,7 +270,8 @@ impl ElectrumExt<ConfirmationHeightAnchor> for Client {
275270
.map(|((_, i), _)| (k, *i))
276271
})
277272
.collect::<BTreeMap<_, _>>();
278-
break update;
273+
274+
break (chain_update, graph_update, keychain_update);
279275
};
280276

281277
Ok(update)
@@ -399,10 +395,10 @@ fn determine_tx_anchor(
399395
}
400396
}
401397

402-
fn populate_with_outpoints<K>(
398+
fn populate_with_outpoints(
403399
client: &Client,
404400
cps: &BTreeMap<u32, CheckPoint>,
405-
update: &mut ElectrumUpdate<K, ConfirmationHeightAnchor>,
401+
graph_update: &mut IncompleteTxGraph<ConfirmationHeightAnchor>,
406402
outpoints: &mut impl Iterator<Item = OutPoint>,
407403
) -> Result<HashMap<Txid, Transaction>, Error> {
408404
let mut full_txs = HashMap::new();
@@ -451,7 +447,7 @@ fn populate_with_outpoints<K>(
451447
};
452448

453449
let anchor = determine_tx_anchor(cps, res.height, res.tx_hash);
454-
let tx_entry = update.graph_update.entry(res.tx_hash).or_default();
450+
let tx_entry = graph_update.0.entry(res.tx_hash).or_default();
455451
if let Some(anchor) = anchor {
456452
tx_entry.insert(anchor);
457453
}
@@ -460,10 +456,10 @@ fn populate_with_outpoints<K>(
460456
Ok(full_txs)
461457
}
462458

463-
fn populate_with_txids<K>(
459+
fn populate_with_txids(
464460
client: &Client,
465461
cps: &BTreeMap<u32, CheckPoint>,
466-
update: &mut ElectrumUpdate<K, ConfirmationHeightAnchor>,
462+
graph_update: &mut IncompleteTxGraph<ConfirmationHeightAnchor>,
467463
txids: &mut impl Iterator<Item = Txid>,
468464
) -> Result<(), Error> {
469465
for txid in txids {
@@ -488,18 +484,18 @@ fn populate_with_txids<K>(
488484
None => continue,
489485
};
490486

491-
let tx_entry = update.graph_update.entry(txid).or_default();
487+
let tx_entry = graph_update.0.entry(txid).or_default();
492488
if let Some(anchor) = anchor {
493489
tx_entry.insert(anchor);
494490
}
495491
}
496492
Ok(())
497493
}
498494

499-
fn populate_with_spks<K, I: Ord + Clone>(
495+
fn populate_with_spks<I: Ord + Clone>(
500496
client: &Client,
501497
cps: &BTreeMap<u32, CheckPoint>,
502-
update: &mut ElectrumUpdate<K, ConfirmationHeightAnchor>,
498+
graph_update: &mut IncompleteTxGraph<ConfirmationHeightAnchor>,
503499
spks: &mut impl Iterator<Item = (I, ScriptBuf)>,
504500
stop_gap: usize,
505501
batch_size: usize,
@@ -532,7 +528,7 @@ fn populate_with_spks<K, I: Ord + Clone>(
532528
}
533529

534530
for tx in spk_history {
535-
let tx_entry = update.graph_update.entry(tx.tx_hash).or_default();
531+
let tx_entry = graph_update.0.entry(tx.tx_hash).or_default();
536532
if let Some(anchor) = determine_tx_anchor(cps, tx.height, tx.tx_hash) {
537533
tx_entry.insert(anchor);
538534
}

crates/electrum/src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
//! This crate is used for updating structures of the [`bdk_chain`] crate with data from electrum.
22
//!
33
//! The star of the show is the [`ElectrumExt::scan`] method, which scans for relevant blockchain
4-
//! data (via electrum) and outputs an [`ElectrumUpdate`].
4+
//! data (via electrum) and outputs updates for [`bdk_chain`] structures as a tuple of form:
55
//!
6-
//! An [`ElectrumUpdate`] only includes `txid`s and no full transactions. The caller is responsible
7-
//! for obtaining full transactions before applying. This can be done with
6+
//! ([`bdk_chain::local_chain::Update`], [`IncompleteTxGraph`], `keychain_update`)
7+
//!
8+
//! An [`IncompleteTxGraph`] only includes `txid`s and no full transactions. The caller is
9+
//! responsible for obtaining full transactions before applying. This can be done with
810
//! these steps:
911
//!
1012
//! 1. Determine which full transactions are missing. The method [`missing_full_txs`] of
11-
//! [`ElectrumUpdate`] can be used.
13+
//! [`IncompleteTxGraph`] can be used.
1214
//!
1315
//! 2. Obtaining the full transactions. To do this via electrum, the method
1416
//! [`batch_transaction_get`] can be used.
1517
//!
1618
//! Refer to [`bdk_electrum_example`] for a complete example.
1719
//!
1820
//! [`ElectrumClient::scan`]: electrum_client::ElectrumClient::scan
19-
//! [`missing_full_txs`]: ElectrumUpdate::missing_full_txs
21+
//! [`missing_full_txs`]: IncompleteTxGraph::missing_full_txs
2022
//! [`batch_transaction_get`]: electrum_client::ElectrumApi::batch_transaction_get
2123
//! [`bdk_electrum_example`]: https://github.com/LLFourn/bdk_core_staging/tree/master/bdk_electrum_example
2224

0 commit comments

Comments
 (0)