]> Untitled Git - bdk/commitdiff
Introduce `keychain::LocalChangeSet`
author志宇 <hello@evanlinjin.me>
Sat, 13 May 2023 15:28:03 +0000 (23:28 +0800)
committer志宇 <hello@evanlinjin.me>
Sat, 3 Jun 2023 19:32:18 +0000 (03:32 +0800)
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
crates/chain/src/indexed_tx_graph.rs
crates/chain/src/keychain.rs
example-crates/example_cli/src/lib.rs
example-crates/example_electrum/src/main.rs

index 5fabc6d1422f2d306739e5ca393f4da47ade2a52..c9e50a9e1dbf0c01dbe51a2b349e6960a044d214 100644 (file)
@@ -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<D = ()> {
 /// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
 pub type Update = LocalUpdate<KeychainKind, ConfirmationTimeAnchor>;
 
-/// 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<K = KeychainKind, A = ConfirmationTimeAnchor> {
-    pub chain_changeset: local_chain::ChangeSet,
-    pub indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>,
-}
-
-impl<K, A> Default for ChangeSet<K, A> {
-    fn default() -> Self {
-        Self {
-            chain_changeset: Default::default(),
-            indexed_additions: Default::default(),
-        }
-    }
-}
-
-impl<K: Ord, A: Anchor> Append for ChangeSet<K, A> {
-    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<K, A> From<IndexedAdditions<A, DerivationAdditions<K>>> for ChangeSet<K, A> {
-    fn from(indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>) -> Self {
-        Self {
-            indexed_additions,
-            ..Default::default()
-        }
-    }
-}
-
-impl<K, A> From<DerivationAdditions<K>> for ChangeSet<K, A> {
-    fn from(index_additions: DerivationAdditions<K>) -> Self {
-        Self {
-            indexed_additions: IndexedAdditions {
-                index_additions,
-                ..Default::default()
-            },
-            ..Default::default()
-        }
-    }
-}
-
-impl<K, A> From<local_chain::ChangeSet> for ChangeSet<K, A> {
-    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<KeychainKind, ConfirmationTimeAnchor>;
 /// 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<D> Wallet<D> {
         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<D> Wallet<D> {
             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<D> Wallet<D> {
         D: PersistBackend<ChangeSet>,
     {
         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();
index 24a1884cb98a18962a9a69a331c222f49379202d..371ba295725fb0a3c500982c047d4c050dab3ea0 100644 (file)
@@ -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<A, IA: Default> From<Additions<A>> for IndexedAdditions<A, IA> {
     }
 }
 
+impl<A, K> From<DerivationAdditions<K>> for IndexedAdditions<A, DerivationAdditions<K>> {
+    fn from(index_additions: DerivationAdditions<K>) -> 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.
index 0f108b2d46ad369fb302c7a9058463d296b12e5d..1a8b0cc4160eb58bb77ec78c2a4f8e141e029058 100644 (file)
 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<K, A> Default for LocalUpdate<K, A> {
     }
 }
 
+/// 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<K, A> {
+    /// Changes to the [`LocalChain`].
+    pub chain_changeset: local_chain::ChangeSet,
+
+    /// Additions to [`IndexedTxGraph`].
+    ///
+    /// [`IndexedTxGraph`]: crate::indexed_tx_graph::IndexedTxGraph
+    pub indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>,
+}
+
+impl<K, A> Default for LocalChangeSet<K, A> {
+    fn default() -> Self {
+        Self {
+            chain_changeset: Default::default(),
+            indexed_additions: Default::default(),
+        }
+    }
+}
+
+impl<K: Ord, A: Anchor> Append for LocalChangeSet<K, A> {
+    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<K, A> From<local_chain::ChangeSet> for LocalChangeSet<K, A> {
+    fn from(chain_changeset: local_chain::ChangeSet) -> Self {
+        Self {
+            chain_changeset,
+            ..Default::default()
+        }
+    }
+}
+
+impl<K, A> From<IndexedAdditions<A, DerivationAdditions<K>>> for LocalChangeSet<K, A> {
+    fn from(indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>) -> Self {
+        Self {
+            indexed_additions,
+            ..Default::default()
+        }
+    }
+}
+
 #[derive(Clone, Debug, PartialEq)]
 /// An update that includes the last active indexes of each keychain.
 pub struct KeychainScan<K, P> {
index 029ccbd4301cbdfdd46dfb75dc44e57f77f90e2e..abb3534336fb7222027849147624521c1614db91 100644 (file)
@@ -26,42 +26,13 @@ pub use clap;
 use clap::{Parser, Subcommand};
 
 pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
-pub type Database<'m, A, X> = Persist<Store<'m, ChangeSet<A, X>>, ChangeSet<A, X>>;
-
-#[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<A, X> {
-    pub indexed_additions: IndexedAdditions<A, DerivationAdditions<Keychain>>,
-    pub extension: X,
-}
-
-impl<A, X: Default> Default for ChangeSet<A, X> {
-    fn default() -> Self {
-        Self {
-            indexed_additions: Default::default(),
-            extension: Default::default(),
-        }
-    }
-}
-
-impl<A: Anchor, X: Append> Append for ChangeSet<A, X> {
-    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<A> = IndexedAdditions<A, DerivationAdditions<Keychain>>;
+pub type Database<'m, C> = Persist<Store<'m, C>, C>;
 
 #[derive(Parser)]
 #[clap(author, version, about, long_about = None)]
 #[clap(propagate_version = true)]
-pub struct Args<C: clap::Subcommand> {
+pub struct Args<S: clap::Subcommand> {
     #[clap(env = "DESCRIPTOR")]
     pub descriptor: String,
     #[clap(env = "CHANGE_DESCRIPTOR")]
@@ -77,14 +48,14 @@ pub struct Args<C: clap::Subcommand> {
     pub cp_limit: usize,
 
     #[clap(subcommand)]
-    pub command: Commands<C>,
+    pub command: Commands<S>,
 }
 
 #[allow(clippy::almost_swapped)]
 #[derive(Subcommand, Debug, Clone)]
-pub enum Commands<C: clap::Subcommand> {
+pub enum Commands<S: clap::Subcommand> {
     #[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<A, X>(
+pub fn run_address_cmd<A, C>(
     graph: &mut KeychainTxGraph<A>,
-    db: &Mutex<Database<'_, A, X>>,
+    db: &Mutex<Database<C>>,
     network: Network,
     cmd: AddressCmd,
 ) -> anyhow::Result<()>
 where
-    ChangeSet<A, X>: Default + Append + DeserializeOwned + Serialize,
+    C: Default + Append + DeserializeOwned + Serialize + From<KeychainAdditions<A>>,
 {
     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<A: Anchor, O: ChainOracle, X>(
+pub fn run_send_cmd<A: Anchor, O: ChainOracle, C>(
     graph: &Mutex<KeychainTxGraph<A>>,
-    db: &Mutex<Database<'_, A, X>>,
+    db: &Mutex<Database<'_, C>>,
     chain: &O,
     keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
     cs_algorithm: CoinSelectionAlgo,
@@ -363,7 +328,7 @@ pub fn run_send_cmd<A: Anchor, O: ChainOracle, X>(
 ) -> anyhow::Result<()>
 where
     O::Error: std::error::Error + Send + Sync + 'static,
-    ChangeSet<A, X>: Default + Append + DeserializeOwned + Serialize,
+    C: Default + Append + DeserializeOwned + Serialize + From<KeychainAdditions<A>>,
 {
     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<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDeri
         .collect()
 }
 
-pub fn handle_commands<C: clap::Subcommand, A: Anchor, O: ChainOracle, X>(
+pub fn handle_commands<S: clap::Subcommand, A: Anchor, O: ChainOracle, C>(
     graph: &Mutex<KeychainTxGraph<A>>,
-    db: &Mutex<Database<A, X>>,
+    db: &Mutex<Database<C>>,
     chain: &Mutex<O>,
     keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
     network: Network,
     broadcast: impl FnOnce(&Transaction) -> anyhow::Result<()>,
-    cmd: Commands<C>,
+    cmd: Commands<S>,
 ) -> anyhow::Result<()>
 where
     O::Error: std::error::Error + Send + Sync + 'static,
-    ChangeSet<A, X>: Default + Append + DeserializeOwned + Serialize,
+    C: Default + Append + DeserializeOwned + Serialize + From<KeychainAdditions<A>>,
 {
     match cmd {
         Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
@@ -708,9 +666,9 @@ where
     }
 }
 
-pub fn prepare_index<C: clap::Subcommand, SC: secp256k1::Signing>(
-    args: &Args<C>,
-    secp: &Secp256k1<SC>,
+pub fn prepare_index<S: clap::Subcommand, CTX: secp256k1::Signing>(
+    args: &Args<S>,
+    secp: &Secp256k1<CTX>,
 ) -> anyhow::Result<(KeychainTxOutIndex<Keychain>, KeyMap)> {
     let mut index = KeychainTxOutIndex::<Keychain>::default();
 
@@ -732,18 +690,18 @@ pub fn prepare_index<C: clap::Subcommand, SC: secp256k1::Signing>(
 }
 
 #[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<S>,
     KeyMap,
-    Mutex<KeychainTxGraph<A>>,
-    Mutex<Database<'m, A, X>>,
-    X,
+    KeychainTxOutIndex<Keychain>,
+    Mutex<Database<'m, C>>,
+    C,
 )>
 where
-    ChangeSet<A, X>: 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::<A, KeychainTxOutIndex<Keychain>>::new(index);
-
-    let mut db_backend =
-        match Store::<'m, ChangeSet<A, X>>::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 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 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,
     ))
 }
index 6b67e8a719b5b4741716459d7dc58da4fce5d15a..42dc747134404db6d0da525c7eab9c63289bd07e 100644 (file)
@@ -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<Keychain, ConfirmationHeightAnchor>;
+
 fn main() -> anyhow::Result<()> {
-    let (args, keymap, graph, db, chain_changeset) =
-        example_cli::init::<ElectrumCommands, ConfirmationHeightAnchor, local_chain::ChangeSet>(
-            DB_MAGIC, DB_PATH,
-        )?;
+    let (args, keymap, index, db, init_changeset) =
+        example_cli::init::<ElectrumCommands, ChangeSet>(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,
         }
     };