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

@@ -5,7 +5,7 @@ use std::{
};
use bdk_chain::{
bitcoin::{Address, BlockHash, Network, OutPoint, Txid},
bitcoin::{Address, Network, OutPoint, Txid},
indexed_tx_graph::{IndexedAdditions, IndexedTxGraph},
keychain::LocalChangeSet,
local_chain::LocalChain,
@@ -22,8 +22,7 @@ use example_cli::{
};
const DB_MAGIC: &[u8] = b"bdk_example_electrum";
const DB_PATH: &str = ".bdk_electrum_example.db";
const ASSUME_FINAL_DEPTH: usize = 10;
const DB_PATH: &str = ".bdk_example_electrum.db";
#[derive(Subcommand, Debug, Clone)]
enum ElectrumCommands {
@@ -73,11 +72,7 @@ fn main() -> anyhow::Result<()> {
graph
});
let chain = Mutex::new({
let mut chain = LocalChain::default();
chain.apply_changeset(init_changeset.chain_changeset);
chain
});
let chain = Mutex::new(LocalChain::from_changeset(init_changeset.chain_changeset));
let electrum_url = match args.network {
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
@@ -119,7 +114,7 @@ fn main() -> anyhow::Result<()> {
stop_gap,
scan_options,
} => {
let (keychain_spks, local_chain) = {
let (keychain_spks, tip) = {
let graph = &*graph.lock().unwrap();
let chain = &*chain.lock().unwrap();
@@ -142,20 +137,13 @@ fn main() -> anyhow::Result<()> {
})
.collect::<BTreeMap<_, _>>();
let c = chain
.blocks()
.iter()
.rev()
.take(ASSUME_FINAL_DEPTH)
.map(|(k, v)| (*k, *v))
.collect::<BTreeMap<u32, BlockHash>>();
(keychain_spks, c)
let tip = chain.tip();
(keychain_spks, tip)
};
client
.scan(
&local_chain,
tip,
keychain_spks,
core::iter::empty(),
core::iter::empty(),
@@ -174,7 +162,7 @@ fn main() -> anyhow::Result<()> {
// Get a short lock on the tracker to get the spks we're interested in
let graph = graph.lock().unwrap();
let chain = chain.lock().unwrap();
let chain_tip = chain.tip().unwrap_or_default();
let chain_tip = chain.tip().map(|cp| cp.block_id()).unwrap_or_default();
if !(all_spks || unused_spks || utxos || unconfirmed) {
unused_spks = true;
@@ -254,23 +242,17 @@ fn main() -> anyhow::Result<()> {
}));
}
let c = chain
.blocks()
.iter()
.rev()
.take(ASSUME_FINAL_DEPTH)
.map(|(k, v)| (*k, *v))
.collect::<BTreeMap<u32, BlockHash>>();
let tip = chain.tip();
// drop lock on graph and chain
drop((graph, chain));
let update = client
.scan_without_keychain(&c, spks, txids, outpoints, scan_options.batch_size)
.scan_without_keychain(tip, spks, txids, outpoints, scan_options.batch_size)
.context("scanning the blockchain")?;
ElectrumUpdate {
graph_update: update.graph_update,
chain_update: update.chain_update,
new_tip: update.new_tip,
keychain_update: BTreeMap::new(),
}
}