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:
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
print!("Syncing...");
|
||||
let client = electrum_client::Client::new("ssl://electrum.blockstream.info:60002")?;
|
||||
|
||||
let local_chain = wallet.checkpoints();
|
||||
let prev_tip = wallet.latest_checkpoint();
|
||||
let keychain_spks = wallet
|
||||
.spks_of_all_keychains()
|
||||
.into_iter()
|
||||
@@ -52,8 +52,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let electrum_update =
|
||||
client.scan(local_chain, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?;
|
||||
let electrum_update = client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?;
|
||||
|
||||
println!();
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
const DB_MAGIC: &str = "bdk_wallet_esplora_example";
|
||||
const SEND_AMOUNT: u64 = 5000;
|
||||
const STOP_GAP: usize = 50;
|
||||
const PARALLEL_REQUESTS: usize = 5;
|
||||
const SEND_AMOUNT: u64 = 1000;
|
||||
const STOP_GAP: usize = 5;
|
||||
const PARALLEL_REQUESTS: usize = 1;
|
||||
|
||||
use std::{io::Write, str::FromStr};
|
||||
|
||||
use bdk::{
|
||||
bitcoin::{Address, Network},
|
||||
chain::keychain::LocalUpdate,
|
||||
wallet::AddressIndex,
|
||||
SignOptions, Wallet,
|
||||
};
|
||||
@@ -36,7 +37,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client =
|
||||
esplora_client::Builder::new("https://blockstream.info/testnet/api").build_blocking()?;
|
||||
|
||||
let local_chain = wallet.checkpoints();
|
||||
let prev_tip = wallet.latest_checkpoint();
|
||||
let keychain_spks = wallet
|
||||
.spks_of_all_keychains()
|
||||
.into_iter()
|
||||
@@ -52,17 +53,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
(k, k_spks)
|
||||
})
|
||||
.collect();
|
||||
let update = client.scan(
|
||||
local_chain,
|
||||
keychain_spks,
|
||||
None,
|
||||
None,
|
||||
STOP_GAP,
|
||||
PARALLEL_REQUESTS,
|
||||
)?;
|
||||
println!();
|
||||
|
||||
let (update_graph, last_active_indices) =
|
||||
client.update_tx_graph(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)?;
|
||||
let get_heights = wallet.tx_graph().missing_blocks(wallet.local_chain());
|
||||
let chain_update = client.update_local_chain(prev_tip, get_heights)?;
|
||||
let update = LocalUpdate {
|
||||
keychain: last_active_indices,
|
||||
graph: update_graph,
|
||||
..LocalUpdate::new(chain_update)
|
||||
};
|
||||
|
||||
wallet.apply_update(update)?;
|
||||
wallet.commit()?;
|
||||
println!();
|
||||
|
||||
let balance = wallet.get_balance();
|
||||
println!("Wallet balance after syncing: {} sats", balance.total());
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{io::Write, str::FromStr};
|
||||
|
||||
use bdk::{
|
||||
bitcoin::{Address, Network},
|
||||
chain::keychain::LocalUpdate,
|
||||
wallet::AddressIndex,
|
||||
SignOptions, Wallet,
|
||||
};
|
||||
@@ -37,7 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client =
|
||||
esplora_client::Builder::new("https://blockstream.info/testnet/api").build_async()?;
|
||||
|
||||
let local_chain = wallet.checkpoints();
|
||||
let prev_tip = wallet.latest_checkpoint();
|
||||
let keychain_spks = wallet
|
||||
.spks_of_all_keychains()
|
||||
.into_iter()
|
||||
@@ -53,19 +54,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
(k, k_spks)
|
||||
})
|
||||
.collect();
|
||||
let update = client
|
||||
.scan(
|
||||
local_chain,
|
||||
keychain_spks,
|
||||
[],
|
||||
[],
|
||||
STOP_GAP,
|
||||
PARALLEL_REQUESTS,
|
||||
)
|
||||
let (update_graph, last_active_indices) = client
|
||||
.update_tx_graph(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)
|
||||
.await?;
|
||||
println!();
|
||||
let get_heights = wallet.tx_graph().missing_blocks(wallet.local_chain());
|
||||
let chain_update = client.update_local_chain(prev_tip, get_heights).await?;
|
||||
let update = LocalUpdate {
|
||||
keychain: last_active_indices,
|
||||
graph: update_graph,
|
||||
..LocalUpdate::new(chain_update)
|
||||
};
|
||||
wallet.apply_update(update)?;
|
||||
wallet.commit()?;
|
||||
println!();
|
||||
|
||||
let balance = wallet.get_balance();
|
||||
println!("Wallet balance after syncing: {} sats", balance.total());
|
||||
|
||||
Reference in New Issue
Block a user