Implement linked-list LocalChain and update chain-src crates/examples

This commit changes the `LocalChain` implementation to have blocks
stored as a linked-list. This allows the data-src thread to hold a
shared ref to a single checkpoint and have access to the whole history
of checkpoints without cloning or keeping a lock on `LocalChain`.

The APIs of `bdk::Wallet`, `esplora` and `electrum` are also updated to
reflect these changes. Note that the `esplora` crate is rewritten to
anchor txs in the confirmation block (using the esplora API's tx status
block_hash). This guarantees 100% consistency between anchor blocks and
their transactions (instead of anchoring txs to the latest tip).
`ExploraExt` now has separate methods for updating the `TxGraph` and
`LocalChain`.

A new method `TxGraph::missing_blocks` is introduced for finding
"floating anchors" of a `TxGraph` update (given a chain).

Additional changes:

* `test_local_chain.rs` is refactored to make test cases easier to
  write. Additional tests are also added.
* Examples are updated.
* Fix `tempfile` dev dependency of `bdk_file_store` to work with MSRV

Co-authored-by: LLFourn <lloyd.fourn@gmail.com>
This commit is contained in:
志宇
2023-07-19 17:42:52 +08:00
parent f4d2a76661
commit eabeb6ccb1
18 changed files with 1551 additions and 904 deletions

View File

@@ -697,7 +697,7 @@ fn test_chain_spends() {
let _ = graph.insert_anchor(
tx.txid(),
ConfirmationHeightAnchor {
anchor_block: tip,
anchor_block: tip.block_id(),
confirmation_height: *ht,
},
);
@@ -705,10 +705,10 @@ fn test_chain_spends() {
// Assert that confirmed spends are returned correctly.
assert_eq!(
graph.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 0)),
graph.get_chain_spend(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 0)),
Some((
ChainPosition::Confirmed(&ConfirmationHeightAnchor {
anchor_block: tip,
anchor_block: tip.block_id(),
confirmation_height: 98
}),
tx_1.txid(),
@@ -717,17 +717,17 @@ fn test_chain_spends() {
// Check if chain position is returned correctly.
assert_eq!(
graph.get_chain_position(&local_chain, tip, tx_0.txid()),
graph.get_chain_position(&local_chain, tip.block_id(), tx_0.txid()),
// Some(ObservedAs::Confirmed(&local_chain.get_block(95).expect("block expected"))),
Some(ChainPosition::Confirmed(&ConfirmationHeightAnchor {
anchor_block: tip,
anchor_block: tip.block_id(),
confirmation_height: 95
}))
);
// Even if unconfirmed tx has a last_seen of 0, it can still be part of a chain spend.
assert_eq!(
graph.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1)),
graph.get_chain_spend(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 1)),
Some((ChainPosition::Unconfirmed(0), tx_2.txid())),
);
@@ -737,7 +737,7 @@ fn test_chain_spends() {
// Check chain spend returned correctly.
assert_eq!(
graph
.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
.get_chain_spend(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 1))
.unwrap(),
(ChainPosition::Unconfirmed(1234567), tx_2.txid())
);
@@ -754,7 +754,7 @@ fn test_chain_spends() {
// Because this tx conflicts with an already confirmed transaction, chain position should return none.
assert!(graph
.get_chain_position(&local_chain, tip, tx_1_conflict.txid())
.get_chain_position(&local_chain, tip.block_id(), tx_1_conflict.txid())
.is_none());
// Another conflicting tx that conflicts with tx_2.
@@ -773,7 +773,7 @@ fn test_chain_spends() {
// This should return a valid observation with correct last seen.
assert_eq!(
graph
.get_chain_position(&local_chain, tip, tx_2_conflict.txid())
.get_chain_position(&local_chain, tip.block_id(), tx_2_conflict.txid())
.expect("position expected"),
ChainPosition::Unconfirmed(1234568)
);
@@ -781,14 +781,14 @@ fn test_chain_spends() {
// Chain_spend now catches the new transaction as the spend.
assert_eq!(
graph
.get_chain_spend(&local_chain, tip, OutPoint::new(tx_0.txid(), 1))
.get_chain_spend(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 1))
.expect("expect observation"),
(ChainPosition::Unconfirmed(1234568), tx_2_conflict.txid())
);
// Chain position of the `tx_2` is now none, as it is older than `tx_2_conflict`
assert!(graph
.get_chain_position(&local_chain, tip, tx_2.txid())
.get_chain_position(&local_chain, tip.block_id(), tx_2.txid())
.is_none());
}