Skip to content

Commit af75d3f

Browse files
committed
[local_chain] Implement LocalChain with linked list
This allows the data-source thread to hold a reference to checkpoints without a lock on `LocalChain` itself. Introduce `LocalChain::update` that replaces `determine_changeset` and `apply_update`. This returns a closure that actually updates `self` when called. This method allows for efficient and elegant updating while being able to "preview" the update before applying. The `LocalChain` update/determine_changeset tests have been updated to also check for the final state after applying the update (not just looking at the changeset). Update `keychain::LocalUpdate` struct to use `CheckPoint` Instead of containing a complete `LocalChain`, the update uses `CheckPoint`. This simplifies the API since updating a `LocalChain` only requires a `CheckPoint` now. The examples and chain source `..Ext` implementations have all been updated to use the new API. Additionally, `..Ext` implementations didn't 100% guarantee consistency of the updates, the logic has been changed to enforce better guarantees.
1 parent 1f16155 commit af75d3f

File tree

18 files changed

+901
-552
lines changed

18 files changed

+901
-552
lines changed

crates/bdk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ dev-getrandom-wasm = ["getrandom/js"]
4747
lazy_static = "1.4"
4848
env_logger = "0.7"
4949
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
50-
base64 = "^0.13"
50+
base64 = "^0.21"
5151
assert_matches = "1.5.0"
5252

5353
[package.metadata.docs.rs]

crates/bdk/src/wallet/mod.rs

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use bdk_chain::keychain::Balance;
2323
use bdk_chain::{
2424
indexed_tx_graph::IndexedAdditions,
2525
keychain::{KeychainTxOutIndex, LocalChangeSet, LocalUpdate},
26-
local_chain::{self, LocalChain, UpdateNotConnectedError},
26+
local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain},
2727
tx_graph::{CanonicalTx, TxGraph},
2828
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut,
2929
IndexedTxGraph, Persist, PersistBackend,
@@ -32,8 +32,8 @@ use bitcoin::consensus::encode::serialize;
3232
use bitcoin::secp256k1::Secp256k1;
3333
use bitcoin::util::psbt;
3434
use bitcoin::{
35-
Address, BlockHash, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script,
36-
Sequence, Transaction, TxOut, Txid, Witness,
35+
Address, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script, Sequence,
36+
Transaction, TxOut, Txid, Witness,
3737
};
3838
use core::fmt;
3939
use core::ops::Deref;
@@ -245,7 +245,7 @@ impl<D> Wallet<D> {
245245
};
246246

247247
let changeset = db.load_from_persistence().map_err(NewError::Persist)?;
248-
chain.apply_changeset(changeset.chain_changeset);
248+
chain.apply_changeset(&changeset.chain_changeset);
249249
indexed_graph.apply_additions(changeset.indexed_additions);
250250

251251
let persist = Persist::new(db);
@@ -370,19 +370,19 @@ impl<D> Wallet<D> {
370370
.graph()
371371
.filter_chain_unspents(
372372
&self.chain,
373-
self.chain.tip().unwrap_or_default(),
373+
self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(),
374374
self.indexed_graph.index.outpoints().iter().cloned(),
375375
)
376376
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
377377
}
378378

379379
/// Get all the checkpoints the wallet is currently storing indexed by height.
380-
pub fn checkpoints(&self) -> &BTreeMap<u32, BlockHash> {
381-
self.chain.blocks()
380+
pub fn checkpoints(&self) -> CheckPointIter {
381+
self.chain.iter_checkpoints(None)
382382
}
383383

384384
/// Returns the latest checkpoint.
385-
pub fn latest_checkpoint(&self) -> Option<BlockId> {
385+
pub fn latest_checkpoint(&self) -> Option<CheckPoint> {
386386
self.chain.tip()
387387
}
388388

@@ -420,7 +420,7 @@ impl<D> Wallet<D> {
420420
.graph()
421421
.filter_chain_unspents(
422422
&self.chain,
423-
self.chain.tip().unwrap_or_default(),
423+
self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(),
424424
core::iter::once((spk_i, op)),
425425
)
426426
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
@@ -437,7 +437,7 @@ impl<D> Wallet<D> {
437437
let canonical_tx = CanonicalTx {
438438
observed_as: graph.get_chain_position(
439439
&self.chain,
440-
self.chain.tip().unwrap_or_default(),
440+
self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(),
441441
txid,
442442
)?,
443443
node: graph.get_tx_node(txid)?,
@@ -460,11 +460,11 @@ impl<D> Wallet<D> {
460460
pub fn insert_checkpoint(
461461
&mut self,
462462
block_id: BlockId,
463-
) -> Result<bool, local_chain::InsertBlockNotMatchingError>
463+
) -> Result<bool, local_chain::InsertBlockError>
464464
where
465465
D: PersistBackend<ChangeSet>,
466466
{
467-
let changeset = self.chain.insert_block(block_id)?;
467+
let (_, changeset) = self.chain.get_or_insert(block_id)?;
468468
let changed = !changeset.is_empty();
469469
self.persist.stage(changeset.into());
470470
Ok(changed)
@@ -500,18 +500,15 @@ impl<D> Wallet<D> {
500500
// anchor tx to checkpoint with lowest height that is >= position's height
501501
let anchor = self
502502
.chain
503-
.blocks()
503+
.checkpoints()
504504
.range(height..)
505505
.next()
506506
.ok_or(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
507-
tip_height: self.chain.tip().map(|b| b.height),
507+
tip_height: self.chain.tip().map(|b| b.height()),
508508
tx_height: height,
509509
})
510-
.map(|(&anchor_height, &anchor_hash)| ConfirmationTimeAnchor {
511-
anchor_block: BlockId {
512-
height: anchor_height,
513-
hash: anchor_hash,
514-
},
510+
.map(|(&_, cp)| ConfirmationTimeAnchor {
511+
anchor_block: cp.block_id(),
515512
confirmation_height: height,
516513
confirmation_time: time,
517514
})?;
@@ -531,17 +528,18 @@ impl<D> Wallet<D> {
531528
pub fn transactions(
532529
&self,
533530
) -> impl Iterator<Item = CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>> + '_ {
534-
self.indexed_graph
535-
.graph()
536-
.list_chain_txs(&self.chain, self.chain.tip().unwrap_or_default())
531+
self.indexed_graph.graph().list_chain_txs(
532+
&self.chain,
533+
self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(),
534+
)
537535
}
538536

539537
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
540538
/// values.
541539
pub fn get_balance(&self) -> Balance {
542540
self.indexed_graph.graph().balance(
543541
&self.chain,
544-
self.chain.tip().unwrap_or_default(),
542+
self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(),
545543
self.indexed_graph.index.outpoints().iter().cloned(),
546544
|&(k, _), _| k == KeychainKind::Internal,
547545
)
@@ -715,8 +713,7 @@ impl<D> Wallet<D> {
715713
None => self
716714
.chain
717715
.tip()
718-
.and_then(|cp| cp.height.into())
719-
.map(|height| LockTime::from_height(height).expect("Invalid height")),
716+
.map(|cp| LockTime::from_height(cp.height()).expect("Invalid height")),
720717
h => h,
721718
};
722719

@@ -1030,7 +1027,7 @@ impl<D> Wallet<D> {
10301027
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
10311028
let graph = self.indexed_graph.graph();
10321029
let txout_index = &self.indexed_graph.index;
1033-
let chain_tip = self.chain.tip().unwrap_or_default();
1030+
let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default();
10341031

10351032
let mut tx = graph
10361033
.get_tx(txid)
@@ -1265,7 +1262,7 @@ impl<D> Wallet<D> {
12651262
psbt: &mut psbt::PartiallySignedTransaction,
12661263
sign_options: SignOptions,
12671264
) -> Result<bool, Error> {
1268-
let chain_tip = self.chain.tip().unwrap_or_default();
1265+
let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default();
12691266

12701267
let tx = &psbt.unsigned_tx;
12711268
let mut finished = true;
@@ -1288,7 +1285,7 @@ impl<D> Wallet<D> {
12881285
});
12891286
let current_height = sign_options
12901287
.assume_height
1291-
.or(self.chain.tip().map(|b| b.height));
1288+
.or(self.chain.tip().map(|b| b.height()));
12921289

12931290
debug!(
12941291
"Input #{} - {}, using `confirmation_height` = {:?}, `current_height` = {:?}",
@@ -1433,7 +1430,7 @@ impl<D> Wallet<D> {
14331430
must_only_use_confirmed_tx: bool,
14341431
current_height: Option<u32>,
14351432
) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
1436-
let chain_tip = self.chain.tip().unwrap_or_default();
1433+
let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default();
14371434
// must_spend <- manually selected utxos
14381435
// may_spend <- all other available utxos
14391436
let mut may_spend = self.get_available_utxos();
@@ -1704,11 +1701,11 @@ impl<D> Wallet<D> {
17041701
/// transactions related to your wallet into it.
17051702
///
17061703
/// [`commit`]: Self::commit
1707-
pub fn apply_update(&mut self, update: Update) -> Result<bool, UpdateNotConnectedError>
1704+
pub fn apply_update(&mut self, update: Update) -> Result<bool, CannotConnectError>
17081705
where
17091706
D: PersistBackend<ChangeSet>,
17101707
{
1711-
let mut changeset: ChangeSet = self.chain.apply_update(update.chain)?.into();
1708+
let mut changeset = ChangeSet::from(self.chain.apply_update(update.tip)?);
17121709
let (_, index_additions) = self
17131710
.indexed_graph
17141711
.index

crates/bdk/tests/wallet.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) ->
4444

4545
fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
4646
let height = match wallet.latest_checkpoint() {
47-
Some(BlockId { height, .. }) => ConfirmationTime::Confirmed { height, time: 0 },
47+
Some(cp) => ConfirmationTime::Confirmed {
48+
height: cp.height(),
49+
time: 0,
50+
},
4851
None => ConfirmationTime::Unconfirmed { last_seen: 0 },
4952
};
5053
receive_output(wallet, value, height)
@@ -222,7 +225,7 @@ fn test_create_tx_fee_sniping_locktime_last_sync() {
222225
// If there's no current_height we're left with using the last sync height
223226
assert_eq!(
224227
psbt.unsigned_tx.lock_time.0,
225-
wallet.latest_checkpoint().unwrap().height
228+
wallet.latest_checkpoint().unwrap().height()
226229
);
227230
}
228231

@@ -1482,7 +1485,7 @@ fn test_bump_fee_drain_wallet() {
14821485
.insert_tx(
14831486
tx.clone(),
14841487
ConfirmationTime::Confirmed {
1485-
height: wallet.latest_checkpoint().unwrap().height,
1488+
height: wallet.latest_checkpoint().unwrap().height(),
14861489
time: 42_000,
14871490
},
14881491
)

crates/chain/src/keychain.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use crate::{
1414
collections::BTreeMap,
1515
indexed_tx_graph::IndexedAdditions,
16-
local_chain::{self, LocalChain},
16+
local_chain::{self, CheckPoint},
1717
tx_graph::TxGraph,
1818
Anchor, Append,
1919
};
@@ -89,24 +89,28 @@ impl<K> AsRef<BTreeMap<K, u32>> for DerivationAdditions<K> {
8989
}
9090
}
9191

92-
/// A structure to update [`KeychainTxOutIndex`], [`TxGraph`] and [`LocalChain`]
93-
/// atomically.
92+
/// A structure to update [`KeychainTxOutIndex`], [`TxGraph`] and [`LocalChain`] atomically.
93+
///
94+
/// [`LocalChain`]: local_chain::LocalChain
9495
#[derive(Debug, Clone, PartialEq)]
9596
pub struct LocalUpdate<K, A> {
9697
/// Last active derivation index per keychain (`K`).
9798
pub keychain: BTreeMap<K, u32>,
9899
/// Update for the [`TxGraph`].
99100
pub graph: TxGraph<A>,
100101
/// Update for the [`LocalChain`].
101-
pub chain: LocalChain,
102+
///
103+
/// [`LocalChain`]: local_chain::LocalChain
104+
pub tip: CheckPoint,
102105
}
103106

104-
impl<K, A> Default for LocalUpdate<K, A> {
105-
fn default() -> Self {
107+
impl<K, A> LocalUpdate<K, A> {
108+
/// Construct a [`LocalUpdate`] with a given [`CheckPoint`] tip.
109+
pub fn new(tip: CheckPoint) -> Self {
106110
Self {
107-
keychain: Default::default(),
108-
graph: Default::default(),
109-
chain: Default::default(),
111+
keychain: BTreeMap::new(),
112+
graph: TxGraph::default(),
113+
tip,
110114
}
111115
}
112116
}
@@ -126,6 +130,8 @@ impl<K, A> Default for LocalUpdate<K, A> {
126130
)]
127131
pub struct LocalChangeSet<K, A> {
128132
/// Changes to the [`LocalChain`].
133+
///
134+
/// [`LocalChain`]: local_chain::LocalChain
129135
pub chain_changeset: local_chain::ChangeSet,
130136

131137
/// Additions to [`IndexedTxGraph`].

0 commit comments

Comments
 (0)