From 50425e979bdbe81621fcd54463cdc7c7aeed90f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sat, 13 May 2023 23:28:03 +0800 Subject: [PATCH] Introduce `keychain::LocalChangeSet` This corresponds to `keychain::KeychainChangeSet` but for the redesigned structures with `LocalChain`. This structure is now used in `Wallet` as well as the examples. --- crates/bdk/src/wallet/mod.rs | 83 ++----------- crates/chain/src/indexed_tx_graph.rs | 10 ++ crates/chain/src/keychain.rs | 66 ++++++++++- example-crates/example_cli/src/lib.rs | 124 ++++++-------------- example-crates/example_electrum/src/main.rs | 26 ++-- 5 files changed, 142 insertions(+), 167 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 5fabc6d1..c9e50a9e 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -22,11 +22,11 @@ use alloc::{ pub use bdk_chain::keychain::Balance; use bdk_chain::{ indexed_tx_graph::{IndexedAdditions, IndexedTxGraph}, - keychain::{DerivationAdditions, KeychainTxOutIndex, LocalUpdate}, + keychain::{KeychainTxOutIndex, LocalChangeSet, LocalUpdate}, local_chain::{self, LocalChain, UpdateNotConnectedError}, tx_graph::{CanonicalTx, TxGraph}, - Anchor, Append, BlockId, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, ObservedAs, - Persist, PersistBackend, + Append, BlockId, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, ObservedAs, Persist, + PersistBackend, }; use bitcoin::consensus::encode::serialize; use bitcoin::secp256k1::Secp256k1; @@ -96,67 +96,8 @@ pub struct Wallet { /// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources. pub type Update = LocalUpdate; -/// The changeset produced internally by applying an update. -#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(bound( - deserialize = "A: Ord + serde::Deserialize<'de>, K: Ord + serde::Deserialize<'de>", - serialize = "A: Ord + serde::Serialize, K: Ord + serde::Serialize" -))] -pub struct ChangeSet { - pub chain_changeset: local_chain::ChangeSet, - pub indexed_additions: IndexedAdditions>, -} - -impl Default for ChangeSet { - fn default() -> Self { - Self { - chain_changeset: Default::default(), - indexed_additions: Default::default(), - } - } -} - -impl Append for ChangeSet { - fn append(&mut self, other: Self) { - Append::append(&mut self.chain_changeset, other.chain_changeset); - Append::append(&mut self.indexed_additions, other.indexed_additions); - } - - fn is_empty(&self) -> bool { - self.chain_changeset.is_empty() && self.indexed_additions.is_empty() - } -} - -impl From>> for ChangeSet { - fn from(indexed_additions: IndexedAdditions>) -> Self { - Self { - indexed_additions, - ..Default::default() - } - } -} - -impl From> for ChangeSet { - fn from(index_additions: DerivationAdditions) -> Self { - Self { - indexed_additions: IndexedAdditions { - index_additions, - ..Default::default() - }, - ..Default::default() - } - } -} - -impl From for ChangeSet { - fn from(chain_changeset: local_chain::ChangeSet) -> Self { - Self { - chain_changeset, - ..Default::default() - } - } -} - +// /// The changeset produced internally by applying an update. +pub(crate) type ChangeSet = LocalChangeSet; /// The address index selection strategy to use to derived an address from the wallet's external /// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`. #[derive(Debug)] @@ -356,10 +297,11 @@ impl Wallet { let txout_index = &mut self.indexed_graph.index; let (index, spk) = match address_index { AddressIndex::New => { - let ((index, spk), changeset) = txout_index.reveal_next_spk(&keychain); + let ((index, spk), index_additions) = txout_index.reveal_next_spk(&keychain); let spk = spk.clone(); - self.persist.stage(changeset.into()); + self.persist + .stage(ChangeSet::from(IndexedAdditions::from(index_additions))); self.persist.commit().expect("TODO"); (index, spk) } @@ -931,11 +873,12 @@ impl Wallet { Some(ref drain_recipient) => drain_recipient.clone(), None => { let change_keychain = self.map_keychain(KeychainKind::Internal); - let ((index, spk), changeset) = + let ((index, spk), index_additions) = self.indexed_graph.index.next_unused_spk(&change_keychain); let spk = spk.clone(); self.indexed_graph.index.mark_used(&change_keychain, index); - self.persist.stage(changeset.into()); + self.persist + .stage(ChangeSet::from(IndexedAdditions::from(index_additions))); self.persist.commit().expect("TODO"); spk } @@ -1751,11 +1694,11 @@ impl Wallet { D: PersistBackend, { let mut changeset: ChangeSet = self.chain.apply_update(update.chain)?.into(); - let (_, derivation_additions) = self + let (_, index_additions) = self .indexed_graph .index .reveal_to_target_multi(&update.keychain); - changeset.append(derivation_additions.into()); + changeset.append(ChangeSet::from(IndexedAdditions::from(index_additions))); changeset.append(self.indexed_graph.apply_update(update.graph).into()); let changed = !changeset.is_empty(); diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 24a1884c..371ba295 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -2,6 +2,7 @@ use alloc::vec::Vec; use bitcoin::{OutPoint, Transaction, TxOut}; use crate::{ + keychain::DerivationAdditions, tx_graph::{Additions, TxGraph}, Anchor, Append, }; @@ -212,6 +213,15 @@ impl From> for IndexedAdditions { } } +impl From> for IndexedAdditions> { + fn from(index_additions: DerivationAdditions) -> Self { + Self { + graph_additions: Default::default(), + index_additions, + } + } +} + /// Represents a structure that can index transaction data. pub trait Indexer { /// The resultant "additions" when new transaction data is indexed. diff --git a/crates/chain/src/keychain.rs b/crates/chain/src/keychain.rs index 0f108b2d..1a8b0cc4 100644 --- a/crates/chain/src/keychain.rs +++ b/crates/chain/src/keychain.rs @@ -18,10 +18,11 @@ use crate::{ chain_graph::{self, ChainGraph}, collections::BTreeMap, - local_chain::LocalChain, + indexed_tx_graph::IndexedAdditions, + local_chain::{self, LocalChain}, sparse_chain::ChainPosition, tx_graph::TxGraph, - Append, ForEachTxOut, + Anchor, Append, ForEachTxOut, }; #[cfg(feature = "miniscript")] @@ -125,6 +126,67 @@ impl Default for LocalUpdate { } } +/// A structure that records the corresponding changes as result of applying an [`LocalUpdate`]. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde( + crate = "serde_crate", + bound( + deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>", + serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize", + ) + ) +)] +pub struct LocalChangeSet { + /// Changes to the [`LocalChain`]. + pub chain_changeset: local_chain::ChangeSet, + + /// Additions to [`IndexedTxGraph`]. + /// + /// [`IndexedTxGraph`]: crate::indexed_tx_graph::IndexedTxGraph + pub indexed_additions: IndexedAdditions>, +} + +impl Default for LocalChangeSet { + fn default() -> Self { + Self { + chain_changeset: Default::default(), + indexed_additions: Default::default(), + } + } +} + +impl Append for LocalChangeSet { + fn append(&mut self, other: Self) { + Append::append(&mut self.chain_changeset, other.chain_changeset); + Append::append(&mut self.indexed_additions, other.indexed_additions); + } + + fn is_empty(&self) -> bool { + self.chain_changeset.is_empty() && self.indexed_additions.is_empty() + } +} + +impl From for LocalChangeSet { + fn from(chain_changeset: local_chain::ChangeSet) -> Self { + Self { + chain_changeset, + ..Default::default() + } + } +} + +impl From>> for LocalChangeSet { + fn from(indexed_additions: IndexedAdditions>) -> Self { + Self { + indexed_additions, + ..Default::default() + } + } +} + #[derive(Clone, Debug, PartialEq)] /// An update that includes the last active indexes of each keychain. pub struct KeychainScan { diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 029ccbd4..abb35343 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -26,42 +26,13 @@ pub use clap; use clap::{Parser, Subcommand}; pub type KeychainTxGraph = IndexedTxGraph>; -pub type Database<'m, A, X> = Persist>, ChangeSet>; - -#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(bound( - deserialize = "A: Ord + serde::Deserialize<'de>, X: serde::Deserialize<'de>", - serialize = "A: Ord + serde::Serialize, X: serde::Serialize", -))] -pub struct ChangeSet { - pub indexed_additions: IndexedAdditions>, - pub extension: X, -} - -impl Default for ChangeSet { - fn default() -> Self { - Self { - indexed_additions: Default::default(), - extension: Default::default(), - } - } -} - -impl Append for ChangeSet { - fn append(&mut self, other: Self) { - Append::append(&mut self.indexed_additions, other.indexed_additions); - Append::append(&mut self.extension, other.extension) - } - - fn is_empty(&self) -> bool { - self.indexed_additions.is_empty() && self.extension.is_empty() - } -} +pub type KeychainAdditions = IndexedAdditions>; +pub type Database<'m, C> = Persist, C>; #[derive(Parser)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] -pub struct Args { +pub struct Args { #[clap(env = "DESCRIPTOR")] pub descriptor: String, #[clap(env = "CHANGE_DESCRIPTOR")] @@ -77,14 +48,14 @@ pub struct Args { pub cp_limit: usize, #[clap(subcommand)] - pub command: Commands, + pub command: Commands, } #[allow(clippy::almost_swapped)] #[derive(Subcommand, Debug, Clone)] -pub enum Commands { +pub enum Commands { #[clap(flatten)] - ChainSpecific(C), + ChainSpecific(S), /// Address generation and inspection. Address { #[clap(subcommand)] @@ -210,14 +181,14 @@ impl core::fmt::Display for Keychain { } } -pub fn run_address_cmd( +pub fn run_address_cmd( graph: &mut KeychainTxGraph, - db: &Mutex>, + db: &Mutex>, network: Network, cmd: AddressCmd, ) -> anyhow::Result<()> where - ChangeSet: Default + Append + DeserializeOwned + Serialize, + C: Default + Append + DeserializeOwned + Serialize + From>, { let index = &mut graph.index; @@ -231,13 +202,7 @@ where let ((spk_i, spk), index_additions) = spk_chooser(index, &Keychain::External); let db = &mut *db.lock().unwrap(); - db.stage(ChangeSet { - indexed_additions: IndexedAdditions { - index_additions, - ..Default::default() - }, - ..Default::default() - }); + db.stage(C::from(KeychainAdditions::from(index_additions))); db.commit()?; let addr = Address::from_script(spk, network).context("failed to derive address")?; println!("[address @ {}] {}", spk_i, addr); @@ -351,9 +316,9 @@ where } #[allow(clippy::too_many_arguments)] -pub fn run_send_cmd( +pub fn run_send_cmd( graph: &Mutex>, - db: &Mutex>, + db: &Mutex>, chain: &O, keymap: &HashMap, cs_algorithm: CoinSelectionAlgo, @@ -363,7 +328,7 @@ pub fn run_send_cmd( ) -> anyhow::Result<()> where O::Error: std::error::Error + Send + Sync + 'static, - ChangeSet: Default + Append + DeserializeOwned + Serialize, + C: Default + Append + DeserializeOwned + Serialize + From>, { let (transaction, change_index) = { let graph = &mut *graph.lock().unwrap(); @@ -374,13 +339,9 @@ where // We must first persist to disk the fact that we've got a new address from the // change keychain so future scans will find the tx we're about to broadcast. // If we're unable to persist this, then we don't want to broadcast. - db.lock().unwrap().stage(ChangeSet { - indexed_additions: IndexedAdditions { - index_additions, - ..Default::default() - }, - ..Default::default() - }); + db.lock() + .unwrap() + .stage(C::from(KeychainAdditions::from(index_additions))); // We don't want other callers/threads to use this address while we're using it // but we also don't want to scan the tx we just created because it's not @@ -396,15 +357,12 @@ where Ok(_) => { println!("Broadcasted Tx : {}", transaction.txid()); - let indexed_additions = graph.lock().unwrap().insert_tx(&transaction, None, None); + let keychain_additions = graph.lock().unwrap().insert_tx(&transaction, None, None); // We know the tx is at least unconfirmed now. Note if persisting here fails, // it's not a big deal since we can always find it again form // blockchain. - db.lock().unwrap().stage(ChangeSet { - indexed_additions, - ..Default::default() - }); + db.lock().unwrap().stage(C::from(keychain_additions)); Ok(()) } Err(e) => { @@ -659,18 +617,18 @@ pub fn planned_utxos( +pub fn handle_commands( graph: &Mutex>, - db: &Mutex>, + db: &Mutex>, chain: &Mutex, keymap: &HashMap, network: Network, broadcast: impl FnOnce(&Transaction) -> anyhow::Result<()>, - cmd: Commands, + cmd: Commands, ) -> anyhow::Result<()> where O::Error: std::error::Error + Send + Sync + 'static, - ChangeSet: Default + Append + DeserializeOwned + Serialize, + C: Default + Append + DeserializeOwned + Serialize + From>, { match cmd { Commands::ChainSpecific(_) => unreachable!("example code should handle this!"), @@ -708,9 +666,9 @@ where } } -pub fn prepare_index( - args: &Args, - secp: &Secp256k1, +pub fn prepare_index( + args: &Args, + secp: &Secp256k1, ) -> anyhow::Result<(KeychainTxOutIndex, KeyMap)> { let mut index = KeychainTxOutIndex::::default(); @@ -732,18 +690,18 @@ pub fn prepare_index( } #[allow(clippy::type_complexity)] -pub fn init<'m, S: clap::Subcommand, A: Anchor, X>( +pub fn init<'m, S: clap::Subcommand, C>( db_magic: &'m [u8], db_default_path: &str, ) -> anyhow::Result<( Args, KeyMap, - Mutex>, - Mutex>, - X, + KeychainTxOutIndex, + Mutex>, + C, )> where - ChangeSet: Default + Append + Serialize + DeserializeOwned, + C: Default + Append + Serialize + DeserializeOwned, { if std::env::var("BDK_DB_PATH").is_err() { std::env::set_var("BDK_DB_PATH", db_default_path); @@ -752,25 +710,19 @@ where let secp = Secp256k1::default(); let (index, keymap) = prepare_index(&args, &secp)?; - let mut indexed_graph = IndexedTxGraph::>::new(index); + let mut db_backend = match Store::<'m, C>::new_from_path(db_magic, &args.db_path) { + Ok(db_backend) => db_backend, + // we cannot return `err` directly as it has lifetime `'m` + Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)), + }; - let mut db_backend = - match Store::<'m, ChangeSet>::new_from_path(db_magic, args.db_path.as_path()) { - Ok(db_backend) => db_backend, - Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)), - }; - - let ChangeSet { - indexed_additions, - extension, - } = db_backend.load_from_persistence()?; - indexed_graph.apply_additions(indexed_additions); + let init_changeset = db_backend.load_from_persistence()?; Ok(( args, keymap, - Mutex::new(indexed_graph), + index, Mutex::new(Database::new(db_backend)), - extension, + init_changeset, )) } diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 6b67e8a7..42dc7471 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -6,8 +6,9 @@ use std::{ use bdk_chain::{ bitcoin::{Address, BlockHash, Network, OutPoint, Txid}, - indexed_tx_graph::IndexedAdditions, - local_chain::{self, LocalChain}, + indexed_tx_graph::{IndexedAdditions, IndexedTxGraph}, + keychain::LocalChangeSet, + local_chain::LocalChain, Append, ConfirmationHeightAnchor, }; use bdk_electrum::{ @@ -17,6 +18,7 @@ use bdk_electrum::{ use example_cli::{ anyhow::{self, Context}, clap::{self, Parser, Subcommand}, + Keychain, }; const DB_MAGIC: &[u8] = b"bdk_example_electrum"; @@ -59,15 +61,21 @@ pub struct ScanOptions { pub batch_size: usize, } +type ChangeSet = LocalChangeSet; + fn main() -> anyhow::Result<()> { - let (args, keymap, graph, db, chain_changeset) = - example_cli::init::( - DB_MAGIC, DB_PATH, - )?; + let (args, keymap, index, db, init_changeset) = + example_cli::init::(DB_MAGIC, DB_PATH)?; + + let graph = Mutex::new({ + let mut graph = IndexedTxGraph::new(index); + graph.apply_additions(init_changeset.indexed_additions); + graph + }); let chain = Mutex::new({ let mut chain = LocalChain::default(); - chain.apply_changeset(chain_changeset); + chain.apply_changeset(init_changeset.chain_changeset); chain }); @@ -302,9 +310,9 @@ fn main() -> anyhow::Result<()> { additions }; - example_cli::ChangeSet { + ChangeSet { indexed_additions, - extension: chain_changeset, + chain_changeset, } };