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

@@ -56,8 +56,8 @@
//! ```
use crate::{
collections::*, keychain::Balance, Anchor, Append, BlockId, ChainOracle, ChainPosition,
ForEachTxOut, FullTxOut,
collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
ChainOracle, ChainPosition, ForEachTxOut, FullTxOut,
};
use alloc::vec::Vec;
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
@@ -598,6 +598,31 @@ impl<A: Clone + Ord> TxGraph<A> {
}
impl<A: Anchor> TxGraph<A> {
/// Find missing block heights of `chain`.
///
/// This works by scanning through anchors, and seeing whether the anchor block of the anchor
/// exists in the [`LocalChain`].
pub fn missing_blocks<'a>(&'a self, chain: &'a LocalChain) -> impl Iterator<Item = u32> + 'a {
self.anchors
.iter()
.map(|(a, _)| a.anchor_block())
.filter({
let mut last_block = Option::<BlockId>::None;
move |block| {
if last_block.as_ref() == Some(block) {
false
} else {
last_block = Some(*block);
true
}
}
})
.filter_map(|block| match chain.heights().get(&block.height) {
Some(chain_hash) if *chain_hash == block.hash => None,
_ => Some(block.height),
})
}
/// Get the position of the transaction in `chain` with tip `chain_tip`.
///
/// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is