Skip to content

Commit 63e3bbe

Browse files
committed
Merge #1403: Update bdk_electrum crate to use sync/full-scan structs
b45897e feat(electrum): update docs and simplify logic of `ElectrumExt` (志宇) 92fb6cb chore(electrum): do not use `anyhow::Result` directly (志宇) b2f3cac feat(electrum): include option for previous `TxOut`s for fee calculation (Wei Chen) c0d7d60 feat(chain)!: use custom return types for `ElectrumExt` methods (志宇) 2945c6b fix(electrum): fixed `sync` functionality (Wei Chen) 9ed33c2 docs(electrum): fixed `full_scan`, `sync`, and crate documentation (Wei Chen) b1f861b feat: update logging of electrum examples (志宇) a6fdfb2 feat(electrum)!: use new sync/full-scan structs for `ElectrumExt` (志宇) 653e4fe feat(wallet): cache txs when constructing full-scan/sync requests (志宇) 58f27b3 feat(chain): introduce `TxCache` to `SyncRequest` and `FullScanRequest` (志宇) 721bb7f fix(chain): Make `Anchor` type in `FullScanResult` generic (志宇) e3cfb84 feat(chain): `TxGraph::insert_tx` reuses `Arc` (志宇) 2ffb656 refactor(electrum): remove `RelevantTxids` and track txs in `TxGraph` (Wei Chen) Pull request description: Fixes #1265 Possibly fixes #1419 ### Context Previous changes such as * Universal structures for full-scan/sync (PR #1413) * Making `CheckPoint` linked list query-able (PR #1369) * Making `Transaction`s cheaply-clonable (PR #1373) has allowed us to simplify the interaction between chain-source and receiving-structures (`bdk_chain`). The motivation is to accomplish something like this ([as mentioned here](#1153 (comment))): ```rust let things_I_am_interested_in = wallet.lock().unwrap().start_sync(); let update = electrum_or_esplora.sync(things_i_am_interested_in)?; wallet.lock().unwrap().apply_update(update)?: ``` ### Description This PR greatly simplifies the API of our Electrum chain-source (`bdk_electrum`) by making use of the aforementioned changes. Instead of referring back to the receiving `TxGraph` mid-sync/scan to determine which full transaction to fetch, we provide the Electrum chain-source already-fetched full transactions to start sync/scan (this is cheap, as transactions are wrapped in `Arc`s since #1373). In addition, an option has been added to include the previous `TxOut` for transactions received from an external wallet for fee calculation. ### Changelog notice * Change `TxGraph::insert_tx` to take in anything that satisfies `Into<Arc<Transaction>>`. This allows us to reuse the `Arc` pointer of what is being inserted. * Add `tx_cache` field to `SyncRequest` and `FullScanRequest`. * Make `Anchor` type in `FullScanResult` generic for more flexibility. * Change `ElectrumExt` methods to take in `SyncRequest`/`FullScanRequest` and return `SyncResult`/`FullScanResult`. Also update electrum examples accordingly. * Add `ElectrumResultExt` trait which allows us to convert the update `TxGraph` of `SyncResult`/`FullScanResult` for `bdk_electrum`. * Added an option for `full_scan` and `sync` to also fetch previous `TxOut`s to allow for fee calculation. ### 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: ValuedMammal: ACK b45897e notmandatory: ACK b45897e Tree-SHA512: 1e274546015e7c7257965b36079ffe0cb3c2c0b7c2e0c322bcf32a06925a0c3e1119da1c8fd5318f1dbd82c2e952f6a07f227a9b023c48f506a62c93045d96d3
2 parents fb7ff29 + b45897e commit 63e3bbe

File tree

9 files changed

+532
-413
lines changed

9 files changed

+532
-413
lines changed

crates/bdk/src/wallet/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2565,6 +2565,7 @@ impl Wallet {
25652565
/// start a blockchain sync with a spk based blockchain client.
25662566
pub fn start_sync_with_revealed_spks(&self) -> SyncRequest {
25672567
SyncRequest::from_chain_tip(self.chain.tip())
2568+
.cache_graph_txs(self.tx_graph())
25682569
.populate_with_revealed_spks(&self.indexed_graph.index, ..)
25692570
}
25702571

@@ -2578,6 +2579,7 @@ impl Wallet {
25782579
/// in which the list of used scripts is not known.
25792580
pub fn start_full_scan(&self) -> FullScanRequest<KeychainKind> {
25802581
FullScanRequest::from_keychain_txout_index(self.chain.tip(), &self.indexed_graph.index)
2582+
.cache_graph_txs(self.tx_graph())
25812583
}
25822584
}
25832585

crates/chain/src/spk_client.rs

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
//! Helper types for spk-based blockchain clients.
22
3+
use crate::{
4+
collections::{BTreeMap, HashMap},
5+
local_chain::CheckPoint,
6+
ConfirmationTimeHeightAnchor, TxGraph,
7+
};
8+
use alloc::{boxed::Box, sync::Arc, vec::Vec};
9+
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, Txid};
310
use core::{fmt::Debug, marker::PhantomData, ops::RangeBounds};
411

5-
use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
6-
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
7-
8-
use crate::{local_chain::CheckPoint, ConfirmationTimeHeightAnchor, TxGraph};
12+
/// A cache of [`Arc`]-wrapped full transactions, identified by their [`Txid`]s.
13+
///
14+
/// This is used by the chain-source to avoid re-fetching full transactions.
15+
pub type TxCache = HashMap<Txid, Arc<Transaction>>;
916

1017
/// Data required to perform a spk-based blockchain client sync.
1118
///
@@ -17,6 +24,8 @@ pub struct SyncRequest {
1724
///
1825
/// [`LocalChain::tip`]: crate::local_chain::LocalChain::tip
1926
pub chain_tip: CheckPoint,
27+
/// Cache of full transactions, so the chain-source can avoid re-fetching.
28+
pub tx_cache: TxCache,
2029
/// Transactions that spend from or to these indexed script pubkeys.
2130
pub spks: Box<dyn ExactSizeIterator<Item = ScriptBuf> + Send>,
2231
/// Transactions with these txids.
@@ -30,12 +39,36 @@ impl SyncRequest {
3039
pub fn from_chain_tip(cp: CheckPoint) -> Self {
3140
Self {
3241
chain_tip: cp,
42+
tx_cache: TxCache::new(),
3343
spks: Box::new(core::iter::empty()),
3444
txids: Box::new(core::iter::empty()),
3545
outpoints: Box::new(core::iter::empty()),
3646
}
3747
}
3848

49+
/// Add to the [`TxCache`] held by the request.
50+
///
51+
/// This consumes the [`SyncRequest`] and returns the updated one.
52+
#[must_use]
53+
pub fn cache_txs<T>(mut self, full_txs: impl IntoIterator<Item = (Txid, T)>) -> Self
54+
where
55+
T: Into<Arc<Transaction>>,
56+
{
57+
self.tx_cache = full_txs
58+
.into_iter()
59+
.map(|(txid, tx)| (txid, tx.into()))
60+
.collect();
61+
self
62+
}
63+
64+
/// Add all transactions from [`TxGraph`] into the [`TxCache`].
65+
///
66+
/// This consumes the [`SyncRequest`] and returns the updated one.
67+
#[must_use]
68+
pub fn cache_graph_txs<A>(self, graph: &TxGraph<A>) -> Self {
69+
self.cache_txs(graph.full_txs().map(|tx_node| (tx_node.txid, tx_node.tx)))
70+
}
71+
3972
/// Set the [`Script`]s that will be synced against.
4073
///
4174
/// This consumes the [`SyncRequest`] and returns the updated one.
@@ -194,6 +227,8 @@ pub struct FullScanRequest<K> {
194227
///
195228
/// [`LocalChain::tip`]: crate::local_chain::LocalChain::tip
196229
pub chain_tip: CheckPoint,
230+
/// Cache of full transactions, so the chain-source can avoid re-fetching.
231+
pub tx_cache: TxCache,
197232
/// Iterators of script pubkeys indexed by the keychain index.
198233
pub spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = (u32, ScriptBuf)> + Send>>,
199234
}
@@ -204,10 +239,34 @@ impl<K: Ord + Clone> FullScanRequest<K> {
204239
pub fn from_chain_tip(chain_tip: CheckPoint) -> Self {
205240
Self {
206241
chain_tip,
242+
tx_cache: TxCache::new(),
207243
spks_by_keychain: BTreeMap::new(),
208244
}
209245
}
210246

247+
/// Add to the [`TxCache`] held by the request.
248+
///
249+
/// This consumes the [`SyncRequest`] and returns the updated one.
250+
#[must_use]
251+
pub fn cache_txs<T>(mut self, full_txs: impl IntoIterator<Item = (Txid, T)>) -> Self
252+
where
253+
T: Into<Arc<Transaction>>,
254+
{
255+
self.tx_cache = full_txs
256+
.into_iter()
257+
.map(|(txid, tx)| (txid, tx.into()))
258+
.collect();
259+
self
260+
}
261+
262+
/// Add all transactions from [`TxGraph`] into the [`TxCache`].
263+
///
264+
/// This consumes the [`SyncRequest`] and returns the updated one.
265+
#[must_use]
266+
pub fn cache_graph_txs<A>(self, graph: &TxGraph<A>) -> Self {
267+
self.cache_txs(graph.full_txs().map(|tx_node| (tx_node.txid, tx_node.tx)))
268+
}
269+
211270
/// Construct a new [`FullScanRequest`] from a given `chain_tip` and `index`.
212271
///
213272
/// Unbounded script pubkey iterators for each keychain (`K`) are extracted using
@@ -316,9 +375,9 @@ impl<K: Ord + Clone> FullScanRequest<K> {
316375
/// Data returned from a spk-based blockchain client full scan.
317376
///
318377
/// See also [`FullScanRequest`].
319-
pub struct FullScanResult<K> {
378+
pub struct FullScanResult<K, A = ConfirmationTimeHeightAnchor> {
320379
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
321-
pub graph_update: TxGraph<ConfirmationTimeHeightAnchor>,
380+
pub graph_update: TxGraph<A>,
322381
/// The update to apply to the receiving [`TxGraph`].
323382
pub chain_update: CheckPoint,
324383
/// Last active indices for the corresponding keychains (`K`).

crates/chain/src/tx_graph.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -516,12 +516,12 @@ impl<A: Clone + Ord> TxGraph<A> {
516516
/// Inserts the given transaction into [`TxGraph`].
517517
///
518518
/// The [`ChangeSet`] returned will be empty if `tx` already exists.
519-
pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> {
519+
pub fn insert_tx<T: Into<Arc<Transaction>>>(&mut self, tx: T) -> ChangeSet<A> {
520+
let tx = tx.into();
520521
let mut update = Self::default();
521-
update.txs.insert(
522-
tx.txid(),
523-
(TxNodeInternal::Whole(tx.into()), BTreeSet::new(), 0),
524-
);
522+
update
523+
.txs
524+
.insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
525525
self.apply_update(update)
526526
}
527527

crates/electrum/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ readme = "README.md"
1212
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1313

1414
[dependencies]
15-
bdk_chain = { path = "../chain", version = "0.13.0", default-features = false }
15+
bdk_chain = { path = "../chain", version = "0.13.0" }
1616
electrum-client = { version = "0.19" }
1717
#rustls = { version = "=0.21.1", optional = true, features = ["dangerous_configuration"] }
1818

0 commit comments

Comments
 (0)