]> Untitled Git - bdk/commitdiff
[bdk_chain_redesign] Introduce `BlockAnchor` trait
author志宇 <hello@evanlinjin.me>
Fri, 24 Mar 2023 01:23:36 +0000 (09:23 +0800)
committer志宇 <hello@evanlinjin.me>
Fri, 24 Mar 2023 04:11:41 +0000 (12:11 +0800)
* Introduce `GraphedTx` struct to access transaction data of graphed
  transactions.
* Ability to insert/access anchors and "seen at" values for graphed
  transactions.
* `Additions` now records changes to anchors and last_seen_at.

21 files changed:
crates/bdk/src/wallet/mod.rs
crates/bdk/src/wallet/tx_builder.rs
crates/chain/src/chain_data.rs
crates/chain/src/chain_graph.rs
crates/chain/src/keychain.rs
crates/chain/src/keychain/persist.rs
crates/chain/src/keychain/tracker.rs
crates/chain/src/sparse_chain.rs
crates/chain/src/tx_data_traits.rs
crates/chain/src/tx_graph.rs
crates/chain/tests/test_chain_graph.rs
crates/chain/tests/test_keychain_tracker.rs
crates/chain/tests/test_tx_graph.rs
crates/electrum/src/lib.rs
crates/esplora/src/async_ext.rs
crates/esplora/src/blocking_ext.rs
crates/file_store/src/file_store.rs
crates/file_store/src/lib.rs
example-crates/keychain_tracker_electrum/src/main.rs
example-crates/keychain_tracker_esplora/src/main.rs
example-crates/keychain_tracker_example_cli/src/lib.rs

index 67032cd3cca9d1f7fcdf8b2babe37cdfbdf27bc3..65d3008b78b1bd604774f5f7be74be4fb4944805 100644 (file)
@@ -23,7 +23,9 @@ pub use bdk_chain::keychain::Balance;
 use bdk_chain::{
     chain_graph,
     keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
-    sparse_chain, BlockId, ConfirmationTime,
+    sparse_chain,
+    tx_graph::GraphedTx,
+    BlockId, ConfirmationTime,
 };
 use bitcoin::consensus::encode::serialize;
 use bitcoin::secp256k1::Secp256k1;
@@ -83,19 +85,19 @@ const COINBASE_MATURITY: u32 = 100;
 pub struct Wallet<D = ()> {
     signers: Arc<SignersContainer>,
     change_signers: Arc<SignersContainer>,
-    keychain_tracker: KeychainTracker<KeychainKind, ConfirmationTime>,
-    persist: persist::Persist<KeychainKind, ConfirmationTime, D>,
+    keychain_tracker: KeychainTracker<KeychainKind, BlockId, ConfirmationTime>,
+    persist: persist::Persist<KeychainKind, BlockId, ConfirmationTime, D>,
     network: Network,
     secp: SecpCtx,
 }
 
 /// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
 /// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
-pub type Update = KeychainScan<KeychainKind, ConfirmationTime>;
+pub type Update = KeychainScan<KeychainKind, BlockId, ConfirmationTime>;
 /// Error indicating that something was wrong with an [`Update<T>`].
 pub type UpdateError = chain_graph::UpdateError<ConfirmationTime>;
 /// The changeset produced internally by applying an update
-pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, ConfirmationTime>;
+pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, BlockId, ConfirmationTime>;
 
 /// 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`.
@@ -195,7 +197,7 @@ impl<D> Wallet<D> {
         network: Network,
     ) -> Result<Self, NewError<D::LoadError>>
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         let secp = Secp256k1::new();
 
@@ -257,7 +259,7 @@ impl<D> Wallet<D> {
     /// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
     pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         self._get_address(address_index, KeychainKind::External)
     }
@@ -271,14 +273,14 @@ impl<D> Wallet<D> {
     /// be returned for any [`AddressIndex`].
     pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         self._get_address(address_index, KeychainKind::Internal)
     }
 
     fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         let keychain = self.map_keychain(keychain);
         let txout_index = &mut self.keychain_tracker.txout_index;
@@ -453,7 +455,11 @@ impl<D> Wallet<D> {
         let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
 
         Some(TransactionDetails {
-            transaction: if include_raw { Some(tx.clone()) } else { None },
+            transaction: if include_raw {
+                Some(tx.tx.clone())
+            } else {
+                None
+            },
             txid,
             received,
             sent,
@@ -518,7 +524,8 @@ impl<D> Wallet<D> {
     /// unconfirmed transactions last.
     pub fn transactions(
         &self,
-    ) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
+    ) -> impl DoubleEndedIterator<Item = (ConfirmationTime, GraphedTx<'_, Transaction, BlockId>)> + '_
+    {
         self.keychain_tracker
             .chain_graph()
             .transactions_in_chain()
@@ -613,7 +620,7 @@ impl<D> Wallet<D> {
         params: TxParams,
     ) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         let external_descriptor = self
             .keychain_tracker
@@ -1027,7 +1034,7 @@ impl<D> Wallet<D> {
             Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
                 return Err(Error::TransactionConfirmed)
             }
-            Some((_, tx)) => tx.clone(),
+            Some((_, tx)) => tx.tx.clone(),
         };
 
         if !tx
@@ -1085,7 +1092,7 @@ impl<D> Wallet<D> {
                                 outpoint: txin.previous_output,
                                 psbt_input: Box::new(psbt::Input {
                                     witness_utxo: Some(txout.clone()),
-                                    non_witness_utxo: Some(prev_tx.clone()),
+                                    non_witness_utxo: Some(prev_tx.tx.clone()),
                                     ..Default::default()
                                 }),
                             },
@@ -1613,7 +1620,7 @@ impl<D> Wallet<D> {
                 psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
             }
             if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
-                psbt_input.non_witness_utxo = Some(prev_tx.clone());
+                psbt_input.non_witness_utxo = Some(prev_tx.tx.clone());
             }
         }
         Ok(psbt_input)
@@ -1687,7 +1694,7 @@ impl<D> Wallet<D> {
     /// [`commit`]: Self::commit
     pub fn apply_update(&mut self, update: Update) -> Result<(), UpdateError>
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         let changeset = self.keychain_tracker.apply_update(update)?;
         self.persist.stage(changeset);
@@ -1699,7 +1706,7 @@ impl<D> Wallet<D> {
     /// [`staged`]: Self::staged
     pub fn commit(&mut self) -> Result<(), D::WriteError>
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         self.persist.commit()
     }
@@ -1717,7 +1724,7 @@ impl<D> Wallet<D> {
     }
 
     /// Get a reference to the inner [`ChainGraph`](bdk_chain::chain_graph::ChainGraph).
-    pub fn as_chain_graph(&self) -> &bdk_chain::chain_graph::ChainGraph<ConfirmationTime> {
+    pub fn as_chain_graph(&self) -> &bdk_chain::chain_graph::ChainGraph<BlockId, ConfirmationTime> {
         self.keychain_tracker.chain_graph()
     }
 }
@@ -1728,8 +1735,8 @@ impl<D> AsRef<bdk_chain::tx_graph::TxGraph> for Wallet<D> {
     }
 }
 
-impl<D> AsRef<bdk_chain::chain_graph::ChainGraph<ConfirmationTime>> for Wallet<D> {
-    fn as_ref(&self) -> &bdk_chain::chain_graph::ChainGraph<ConfirmationTime> {
+impl<D> AsRef<bdk_chain::chain_graph::ChainGraph<BlockId, ConfirmationTime>> for Wallet<D> {
+    fn as_ref(&self) -> &bdk_chain::chain_graph::ChainGraph<BlockId, ConfirmationTime> {
         self.keychain_tracker.chain_graph()
     }
 }
index dbd4811c182267fd7c1261ff6ee59e3b7847217d..150d33aa07b54e20e93d4af7424d938938aaa7b2 100644 (file)
@@ -39,6 +39,7 @@
 use crate::collections::BTreeMap;
 use crate::collections::HashSet;
 use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
+use bdk_chain::BlockId;
 use bdk_chain::ConfirmationTime;
 use core::cell::RefCell;
 use core::marker::PhantomData;
@@ -526,7 +527,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
     /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
     pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
     where
-        D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
+        D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
     {
         self.wallet
             .borrow_mut()
index 59444d7f90eb5c6f8fd2d34c255b37bce3d86994..ec76dbb7dba3882ce03e1e2b170f4c8cd6116487 100644 (file)
@@ -2,7 +2,7 @@ use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
 
 use crate::{
     sparse_chain::{self, ChainPosition},
-    COINBASE_MATURITY,
+    BlockAnchor, COINBASE_MATURITY,
 };
 
 /// Represents the height at which a transaction is confirmed.
@@ -118,7 +118,7 @@ impl ConfirmationTime {
 }
 
 /// A reference to a block in the canonical chain.
-#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord)]
+#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Deserialize, serde::Serialize),
@@ -140,6 +140,12 @@ impl Default for BlockId {
     }
 }
 
+impl BlockAnchor for BlockId {
+    fn anchor_block(&self) -> BlockId {
+        *self
+    }
+}
+
 impl From<(u32, BlockHash)> for BlockId {
     fn from((height, hash): (u32, BlockHash)) -> Self {
         Self { height, hash }
index acf104e79d2fd9ca25849969896c83f482fa1250..1a6ccb1e01be76e208749ca52b3eb88e9d2c5aee 100644 (file)
@@ -2,8 +2,8 @@
 use crate::{
     collections::HashSet,
     sparse_chain::{self, ChainPosition, SparseChain},
-    tx_graph::{self, TxGraph},
-    BlockId, ForEachTxOut, FullTxOut, TxHeight,
+    tx_graph::{self, GraphedTx, TxGraph},
+    BlockAnchor, BlockId, ForEachTxOut, FullTxOut, TxHeight,
 };
 use alloc::{string::ToString, vec::Vec};
 use bitcoin::{OutPoint, Transaction, TxOut, Txid};
@@ -25,12 +25,12 @@ use core::fmt::Debug;
 /// `graph` but not the other way around. Transactions may fall out of the *chain* (via re-org or
 /// mempool eviction) but will remain in the *graph*.
 #[derive(Clone, Debug, PartialEq)]
-pub struct ChainGraph<P = TxHeight> {
+pub struct ChainGraph<A = BlockId, P = TxHeight> {
     chain: SparseChain<P>,
-    graph: TxGraph,
+    graph: TxGraph<A>,
 }
 
-impl<P> Default for ChainGraph<P> {
+impl<A, P> Default for ChainGraph<A, P> {
     fn default() -> Self {
         Self {
             chain: Default::default(),
@@ -39,38 +39,39 @@ impl<P> Default for ChainGraph<P> {
     }
 }
 
-impl<P> AsRef<SparseChain<P>> for ChainGraph<P> {
+impl<A, P> AsRef<SparseChain<P>> for ChainGraph<A, P> {
     fn as_ref(&self) -> &SparseChain<P> {
         &self.chain
     }
 }
 
-impl<P> AsRef<TxGraph> for ChainGraph<P> {
-    fn as_ref(&self) -> &TxGraph {
+impl<A, P> AsRef<TxGraph<A>> for ChainGraph<A, P> {
+    fn as_ref(&self) -> &TxGraph<A> {
         &self.graph
     }
 }
 
-impl<P> AsRef<ChainGraph<P>> for ChainGraph<P> {
-    fn as_ref(&self) -> &ChainGraph<P> {
+impl<A, P> AsRef<ChainGraph<A, P>> for ChainGraph<A, P> {
+    fn as_ref(&self) -> &ChainGraph<A, P> {
         self
     }
 }
 
-impl<P> ChainGraph<P> {
+impl<A, P> ChainGraph<A, P> {
     /// Returns a reference to the internal [`SparseChain`].
     pub fn chain(&self) -> &SparseChain<P> {
         &self.chain
     }
 
     /// Returns a reference to the internal [`TxGraph`].
-    pub fn graph(&self) -> &TxGraph {
+    pub fn graph(&self) -> &TxGraph<A> {
         &self.graph
     }
 }
 
-impl<P> ChainGraph<P>
+impl<A, P> ChainGraph<A, P>
 where
+    A: BlockAnchor,
     P: ChainPosition,
 {
     /// Create a new chain graph from a `chain` and a `graph`.
@@ -81,12 +82,14 @@ where
     /// transaction in `graph`.
     /// 2. The `chain` has two transactions that are allegedly in it, but they conflict in the `graph`
     /// (so could not possibly be in the same chain).
-    pub fn new(chain: SparseChain<P>, graph: TxGraph) -> Result<Self, NewError<P>> {
+    pub fn new(chain: SparseChain<P>, graph: TxGraph<A>) -> Result<Self, NewError<P>> {
         let mut missing = HashSet::default();
         for (pos, txid) in chain.txids() {
-            if let Some(tx) = graph.get_tx(*txid) {
+            if let Some(graphed_tx) = graph.get_tx(*txid) {
                 let conflict = graph
-                    .walk_conflicts(tx, |_, txid| Some((chain.tx_position(txid)?.clone(), txid)))
+                    .walk_conflicts(graphed_tx.tx, |_, txid| {
+                        Some((chain.tx_position(txid)?.clone(), txid))
+                    })
                     .next();
                 if let Some((conflict_pos, conflict)) = conflict {
                     return Err(NewError::Conflict {
@@ -126,7 +129,7 @@ where
         &self,
         update: SparseChain<P>,
         new_txs: impl IntoIterator<Item = Transaction>,
-    ) -> Result<ChainGraph<P>, NewError<P>> {
+    ) -> Result<ChainGraph<A, P>, NewError<P>> {
         let mut inflated_chain = SparseChain::default();
         let mut inflated_graph = TxGraph::default();
 
@@ -143,7 +146,7 @@ where
             match self.chain.tx_position(*txid) {
                 Some(original_pos) => {
                     if original_pos != pos {
-                        let tx = self
+                        let graphed_tx = self
                             .graph
                             .get_tx(*txid)
                             .expect("tx must exist as it is referenced in sparsechain")
@@ -151,7 +154,7 @@ where
                         let _ = inflated_chain
                             .insert_tx(*txid, pos.clone())
                             .expect("must insert since this was already in update");
-                        let _ = inflated_graph.insert_tx(tx);
+                        let _ = inflated_graph.insert_tx(graphed_tx.tx.clone());
                     }
                 }
                 None => {
@@ -185,7 +188,7 @@ where
 
     /// Determines the changes required to invalidate checkpoints `from_height` (inclusive) and
     /// above. Displaced transactions will have their positions moved to [`TxHeight::Unconfirmed`].
-    pub fn invalidate_checkpoints_preview(&self, from_height: u32) -> ChangeSet<P> {
+    pub fn invalidate_checkpoints_preview(&self, from_height: u32) -> ChangeSet<A, P> {
         ChangeSet {
             chain: self.chain.invalidate_checkpoints_preview(from_height),
             ..Default::default()
@@ -197,9 +200,9 @@ where
     ///
     /// This is equivalent to calling [`Self::invalidate_checkpoints_preview`] and
     /// [`Self::apply_changeset`] in sequence.
-    pub fn invalidate_checkpoints(&mut self, from_height: u32) -> ChangeSet<P>
+    pub fn invalidate_checkpoints(&mut self, from_height: u32) -> ChangeSet<A, P>
     where
-        ChangeSet<P>: Clone,
+        ChangeSet<A, P>: Clone,
     {
         let changeset = self.invalidate_checkpoints_preview(from_height);
         self.apply_changeset(changeset.clone());
@@ -210,10 +213,10 @@ where
     ///
     /// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in
     /// the unconfirmed transaction list within the [`SparseChain`].
-    pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> {
+    pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, GraphedTx<'_, Transaction, A>)> {
         let position = self.chain.tx_position(txid)?;
-        let full_tx = self.graph.get_tx(txid).expect("must exist");
-        Some((position, full_tx))
+        let graphed_tx = self.graph.get_tx(txid).expect("must exist");
+        Some((position, graphed_tx))
     }
 
     /// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
@@ -225,7 +228,7 @@ where
         &self,
         tx: Transaction,
         pos: P,
-    ) -> Result<ChangeSet<P>, InsertTxError<P>> {
+    ) -> Result<ChangeSet<A, P>, InsertTxError<P>> {
         let mut changeset = ChangeSet {
             chain: self.chain.insert_tx_preview(tx.txid(), pos)?,
             graph: self.graph.insert_tx_preview(tx),
@@ -238,14 +241,18 @@ where
     ///
     /// This is equivalent to calling [`Self::insert_tx_preview`] and [`Self::apply_changeset`] in
     /// sequence.
-    pub fn insert_tx(&mut self, tx: Transaction, pos: P) -> Result<ChangeSet<P>, InsertTxError<P>> {
+    pub fn insert_tx(
+        &mut self,
+        tx: Transaction,
+        pos: P,
+    ) -> Result<ChangeSet<A, P>, InsertTxError<P>> {
         let changeset = self.insert_tx_preview(tx, pos)?;
         self.apply_changeset(changeset.clone());
         Ok(changeset)
     }
 
     /// Determines the changes required to insert a [`TxOut`] into the internal [`TxGraph`].
-    pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<P> {
+    pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, P> {
         ChangeSet {
             chain: Default::default(),
             graph: self.graph.insert_txout_preview(outpoint, txout),
@@ -256,7 +263,7 @@ where
     ///
     /// This is equivalent to calling [`Self::insert_txout_preview`] and [`Self::apply_changeset`]
     /// in sequence.
-    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<P> {
+    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, P> {
         let changeset = self.insert_txout_preview(outpoint, txout);
         self.apply_changeset(changeset.clone());
         changeset
@@ -269,7 +276,7 @@ where
     pub fn insert_checkpoint_preview(
         &self,
         block_id: BlockId,
-    ) -> Result<ChangeSet<P>, InsertCheckpointError> {
+    ) -> Result<ChangeSet<A, P>, InsertCheckpointError> {
         self.chain
             .insert_checkpoint_preview(block_id)
             .map(|chain_changeset| ChangeSet {
@@ -285,7 +292,7 @@ where
     pub fn insert_checkpoint(
         &mut self,
         block_id: BlockId,
-    ) -> Result<ChangeSet<P>, InsertCheckpointError> {
+    ) -> Result<ChangeSet<A, P>, InsertCheckpointError> {
         let changeset = self.insert_checkpoint_preview(block_id)?;
         self.apply_changeset(changeset.clone());
         Ok(changeset)
@@ -294,8 +301,8 @@ where
     /// Calculates the difference between self and `update` in the form of a [`ChangeSet`].
     pub fn determine_changeset(
         &self,
-        update: &ChainGraph<P>,
-    ) -> Result<ChangeSet<P>, UpdateError<P>> {
+        update: &ChainGraph<A, P>,
+    ) -> Result<ChangeSet<A, P>, UpdateError<P>> {
         let chain_changeset = self
             .chain
             .determine_changeset(&update.chain)
@@ -330,7 +337,10 @@ where
     ///
     /// **WARNING:** If there are any missing full txs, conflict resolution will not be complete. In
     /// debug mode, this will result in panic.
-    fn fix_conflicts(&self, changeset: &mut ChangeSet<P>) -> Result<(), UnresolvableConflict<P>> {
+    fn fix_conflicts(
+        &self,
+        changeset: &mut ChangeSet<A, P>,
+    ) -> Result<(), UnresolvableConflict<P>> {
         let mut chain_conflicts = vec![];
 
         for (&txid, pos_change) in &changeset.chain.txids {
@@ -346,7 +356,7 @@ where
                 None => continue,
             };
 
-            let mut full_tx = self.graph.get_tx(txid);
+            let mut full_tx = self.graph.get_tx(txid).map(|tx| tx.tx);
 
             if full_tx.is_none() {
                 full_tx = changeset.graph.tx.iter().find(|tx| tx.txid() == txid)
@@ -406,14 +416,17 @@ where
     ///
     /// **Warning** this method assumes that the changeset is correctly formed. If it is not, the
     /// chain graph may behave incorrectly in the future and panic unexpectedly.
-    pub fn apply_changeset(&mut self, changeset: ChangeSet<P>) {
+    pub fn apply_changeset(&mut self, changeset: ChangeSet<A, P>) {
         self.chain.apply_changeset(changeset.chain);
         self.graph.apply_additions(changeset.graph);
     }
 
     /// Applies the `update` chain graph. Note this is shorthand for calling
     /// [`Self::determine_changeset()`] and [`Self::apply_changeset()`] in sequence.
-    pub fn apply_update(&mut self, update: ChainGraph<P>) -> Result<ChangeSet<P>, UpdateError<P>> {
+    pub fn apply_update(
+        &mut self,
+        update: ChainGraph<A, P>,
+    ) -> Result<ChangeSet<A, P>, UpdateError<P>> {
         let changeset = self.determine_changeset(&update)?;
         self.apply_changeset(changeset.clone());
         Ok(changeset)
@@ -426,7 +439,9 @@ where
 
     /// Iterate over the full transactions and their position in the chain ordered by their position
     /// in ascending order.
-    pub fn transactions_in_chain(&self) -> impl DoubleEndedIterator<Item = (&P, &Transaction)> {
+    pub fn transactions_in_chain(
+        &self,
+    ) -> impl DoubleEndedIterator<Item = (&P, GraphedTx<'_, Transaction, A>)> {
         self.chain
             .txids()
             .map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
@@ -457,18 +472,18 @@ where
     serde(
         crate = "serde_crate",
         bound(
-            deserialize = "P: serde::Deserialize<'de>",
-            serialize = "P: serde::Serialize"
+            deserialize = "A: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>",
+            serialize = "A: Ord + serde::Serialize, P: serde::Serialize"
         )
     )
 )]
 #[must_use]
-pub struct ChangeSet<P> {
+pub struct ChangeSet<A, P> {
     pub chain: sparse_chain::ChangeSet<P>,
-    pub graph: tx_graph::Additions,
+    pub graph: tx_graph::Additions<A>,
 }
 
-impl<P> ChangeSet<P> {
+impl<A, P> ChangeSet<A, P> {
     /// Returns `true` if this [`ChangeSet`] records no changes.
     pub fn is_empty(&self) -> bool {
         self.chain.is_empty() && self.graph.is_empty()
@@ -484,7 +499,7 @@ impl<P> ChangeSet<P> {
 
     /// Appends the changes in `other` into self such that applying `self` afterward has the same
     /// effect as sequentially applying the original `self` and `other`.
-    pub fn append(&mut self, other: ChangeSet<P>)
+    pub fn append(&mut self, other: ChangeSet<A, P>)
     where
         P: ChainPosition,
     {
@@ -493,7 +508,7 @@ impl<P> ChangeSet<P> {
     }
 }
 
-impl<P> Default for ChangeSet<P> {
+impl<A, P> Default for ChangeSet<A, P> {
     fn default() -> Self {
         Self {
             chain: Default::default(),
@@ -508,7 +523,7 @@ impl<P> ForEachTxOut for ChainGraph<P> {
     }
 }
 
-impl<P> ForEachTxOut for ChangeSet<P> {
+impl<A, P> ForEachTxOut for ChangeSet<A, P> {
     fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
         self.graph.for_each_txout(f)
     }
index 32176936080597fa59b03056075ed00c34be0af7..92d72841fa165a0a81a8fd34a80dd2fab692a6e0 100644 (file)
@@ -99,14 +99,14 @@ impl<K> AsRef<BTreeMap<K, u32>> for DerivationAdditions<K> {
 
 #[derive(Clone, Debug, PartialEq)]
 /// An update that includes the last active indexes of each keychain.
-pub struct KeychainScan<K, P> {
+pub struct KeychainScan<K, A, P> {
     /// The update data in the form of a chain that could be applied
-    pub update: ChainGraph<P>,
+    pub update: ChainGraph<A, P>,
     /// The last active indexes of each keychain
     pub last_active_indices: BTreeMap<K, u32>,
 }
 
-impl<K, P> Default for KeychainScan<K, P> {
+impl<K, A: Default, P> Default for KeychainScan<K, A, P> {
     fn default() -> Self {
         Self {
             update: Default::default(),
@@ -115,8 +115,8 @@ impl<K, P> Default for KeychainScan<K, P> {
     }
 }
 
-impl<K, P> From<ChainGraph<P>> for KeychainScan<K, P> {
-    fn from(update: ChainGraph<P>) -> Self {
+impl<K, A, P> From<ChainGraph<A, P>> for KeychainScan<K, A, P> {
+    fn from(update: ChainGraph<A, P>) -> Self {
         KeychainScan {
             update,
             last_active_indices: Default::default(),
@@ -134,20 +134,20 @@ impl<K, P> From<ChainGraph<P>> for KeychainScan<K, P> {
     serde(
         crate = "serde_crate",
         bound(
-            deserialize = "K: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>",
-            serialize = "K: Ord + serde::Serialize, P: serde::Serialize"
+            deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>",
+            serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize, P: serde::Serialize"
         )
     )
 )]
 #[must_use]
-pub struct KeychainChangeSet<K, P> {
+pub struct KeychainChangeSet<K, A, P> {
     /// The changes in local keychain derivation indices
     pub derivation_indices: DerivationAdditions<K>,
     /// The changes that have occurred in the blockchain
-    pub chain_graph: chain_graph::ChangeSet<P>,
+    pub chain_graph: chain_graph::ChangeSet<A, P>,
 }
 
-impl<K, P> Default for KeychainChangeSet<K, P> {
+impl<K, A, P> Default for KeychainChangeSet<K, A, P> {
     fn default() -> Self {
         Self {
             chain_graph: Default::default(),
@@ -156,7 +156,7 @@ impl<K, P> Default for KeychainChangeSet<K, P> {
     }
 }
 
-impl<K, P> KeychainChangeSet<K, P> {
+impl<K, A, P> KeychainChangeSet<K, A, P> {
     /// Returns whether the [`KeychainChangeSet`] is empty (no changes recorded).
     pub fn is_empty(&self) -> bool {
         self.chain_graph.is_empty() && self.derivation_indices.is_empty()
@@ -167,7 +167,7 @@ impl<K, P> KeychainChangeSet<K, P> {
     ///
     /// Note the derivation indices cannot be decreased, so `other` will only change the derivation
     /// index for a keychain, if it's value is higher than the one in `self`.
-    pub fn append(&mut self, other: KeychainChangeSet<K, P>)
+    pub fn append(&mut self, other: KeychainChangeSet<K, A, P>)
     where
         K: Ord,
         P: ChainPosition,
@@ -177,8 +177,8 @@ impl<K, P> KeychainChangeSet<K, P> {
     }
 }
 
-impl<K, P> From<chain_graph::ChangeSet<P>> for KeychainChangeSet<K, P> {
-    fn from(changeset: chain_graph::ChangeSet<P>) -> Self {
+impl<K, A, P> From<chain_graph::ChangeSet<A, P>> for KeychainChangeSet<K, A, P> {
+    fn from(changeset: chain_graph::ChangeSet<A, P>) -> Self {
         Self {
             chain_graph: changeset,
             ..Default::default()
@@ -186,7 +186,7 @@ impl<K, P> From<chain_graph::ChangeSet<P>> for KeychainChangeSet<K, P> {
     }
 }
 
-impl<K, P> From<DerivationAdditions<K>> for KeychainChangeSet<K, P> {
+impl<K, A, P> From<DerivationAdditions<K>> for KeychainChangeSet<K, A, P> {
     fn from(additions: DerivationAdditions<K>) -> Self {
         Self {
             derivation_indices: additions,
@@ -195,13 +195,13 @@ impl<K, P> From<DerivationAdditions<K>> for KeychainChangeSet<K, P> {
     }
 }
 
-impl<K, P> AsRef<TxGraph> for KeychainScan<K, P> {
-    fn as_ref(&self) -> &TxGraph {
+impl<K, A, P> AsRef<TxGraph<A>> for KeychainScan<K, A, P> {
+    fn as_ref(&self) -> &TxGraph<A> {
         self.update.graph()
     }
 }
 
-impl<K, P> ForEachTxOut for KeychainChangeSet<K, P> {
+impl<K, A, P> ForEachTxOut for KeychainChangeSet<K, A, P> {
     fn for_each_txout(&self, f: impl FnMut((bitcoin::OutPoint, &bitcoin::TxOut))) {
         self.chain_graph.for_each_txout(f)
     }
@@ -287,12 +287,12 @@ mod test {
         rhs_di.insert(Keychain::Four, 4);
         let mut lhs = KeychainChangeSet {
             derivation_indices: DerivationAdditions(lhs_di),
-            chain_graph: chain_graph::ChangeSet::<TxHeight>::default(),
+            chain_graph: chain_graph::ChangeSet::<(), TxHeight>::default(),
         };
 
         let rhs = KeychainChangeSet {
             derivation_indices: DerivationAdditions(rhs_di),
-            chain_graph: chain_graph::ChangeSet::<TxHeight>::default(),
+            chain_graph: chain_graph::ChangeSet::<(), TxHeight>::default(),
         };
 
         lhs.append(rhs);
index 1a3ffab02fdeb7a1ac9a0bb54187e48e94cce054..f0bc8d1164c1e5218fa106f4651698a8d732febb 100644 (file)
@@ -18,12 +18,12 @@ use crate::{keychain, sparse_chain::ChainPosition};
 ///
 /// [`KeychainTracker`]: keychain::KeychainTracker
 #[derive(Debug)]
-pub struct Persist<K, P, B> {
+pub struct Persist<K, A, P, B> {
     backend: B,
-    stage: keychain::KeychainChangeSet<K, P>,
+    stage: keychain::KeychainChangeSet<K, A, P>,
 }
 
-impl<K, P, B> Persist<K, P, B> {
+impl<K, A, P, B> Persist<K, A, P, B> {
     /// Create a new `Persist` from a [`PersistBackend`].
     pub fn new(backend: B) -> Self {
         Self {
@@ -35,7 +35,7 @@ impl<K, P, B> Persist<K, P, B> {
     /// Stage a `changeset` to later persistence with [`commit`].
     ///
     /// [`commit`]: Self::commit
-    pub fn stage(&mut self, changeset: keychain::KeychainChangeSet<K, P>)
+    pub fn stage(&mut self, changeset: keychain::KeychainChangeSet<K, A, P>)
     where
         K: Ord,
         P: ChainPosition,
@@ -44,7 +44,7 @@ impl<K, P, B> Persist<K, P, B> {
     }
 
     /// Get the changes that haven't been committed yet
-    pub fn staged(&self) -> &keychain::KeychainChangeSet<K, P> {
+    pub fn staged(&self) -> &keychain::KeychainChangeSet<K, A, P> {
         &self.stage
     }
 
@@ -53,7 +53,7 @@ impl<K, P, B> Persist<K, P, B> {
     /// Returns a backend-defined error if this fails.
     pub fn commit(&mut self) -> Result<(), B::WriteError>
     where
-        B: PersistBackend<K, P>,
+        B: PersistBackend<K, A, P>,
     {
         self.backend.append_changeset(&self.stage)?;
         self.stage = Default::default();
@@ -62,7 +62,7 @@ impl<K, P, B> Persist<K, P, B> {
 }
 
 /// A persistence backend for [`Persist`].
-pub trait PersistBackend<K, P> {
+pub trait PersistBackend<K, A, P> {
     /// The error the backend returns when it fails to write.
     type WriteError: core::fmt::Debug;
 
@@ -79,29 +79,29 @@ pub trait PersistBackend<K, P> {
     /// [`load_into_keychain_tracker`]: Self::load_into_keychain_tracker
     fn append_changeset(
         &mut self,
-        changeset: &keychain::KeychainChangeSet<K, P>,
+        changeset: &keychain::KeychainChangeSet<K, A, P>,
     ) -> Result<(), Self::WriteError>;
 
     /// Applies all the changesets the backend has received to `tracker`.
     fn load_into_keychain_tracker(
         &mut self,
-        tracker: &mut keychain::KeychainTracker<K, P>,
+        tracker: &mut keychain::KeychainTracker<K, A, P>,
     ) -> Result<(), Self::LoadError>;
 }
 
-impl<K, P> PersistBackend<K, P> for () {
+impl<K, A, P> PersistBackend<K, A, P> for () {
     type WriteError = ();
     type LoadError = ();
 
     fn append_changeset(
         &mut self,
-        _changeset: &keychain::KeychainChangeSet<K, P>,
+        _changeset: &keychain::KeychainChangeSet<K, A, P>,
     ) -> Result<(), Self::WriteError> {
         Ok(())
     }
     fn load_into_keychain_tracker(
         &mut self,
-        _tracker: &mut keychain::KeychainTracker<K, P>,
+        _tracker: &mut keychain::KeychainTracker<K, A, P>,
     ) -> Result<(), Self::LoadError> {
         Ok(())
     }
index fff5ee2b4da24d037a501984009b5557de883a0f..db4e8d893d0b7fd33a5f1bdd80c5affbd74ec9b0 100644 (file)
@@ -17,15 +17,16 @@ use super::{Balance, DerivationAdditions};
 /// The [`KeychainTracker`] atomically updates its [`KeychainTxOutIndex`] whenever new chain data is
 /// incorporated into its internal [`ChainGraph`].
 #[derive(Clone, Debug)]
-pub struct KeychainTracker<K, P> {
+pub struct KeychainTracker<K, A, P> {
     /// Index between script pubkeys to transaction outputs
     pub txout_index: KeychainTxOutIndex<K>,
-    chain_graph: ChainGraph<P>,
+    chain_graph: ChainGraph<A, P>,
 }
 
-impl<K, P> KeychainTracker<K, P>
+impl<K, A, P> KeychainTracker<K, A, P>
 where
     P: sparse_chain::ChainPosition,
+    A: crate::BlockAnchor,
     K: Ord + Clone + core::fmt::Debug,
 {
     /// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses.
@@ -64,8 +65,8 @@ where
     /// [`KeychainTxOutIndex`].
     pub fn determine_changeset(
         &self,
-        scan: &KeychainScan<K, P>,
-    ) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
+        scan: &KeychainScan<K, A, P>,
+    ) -> Result<KeychainChangeSet<K, A, P>, chain_graph::UpdateError<P>> {
         // TODO: `KeychainTxOutIndex::determine_additions`
         let mut derivation_indices = scan.last_active_indices.clone();
         derivation_indices.retain(|keychain, index| {
@@ -89,8 +90,8 @@ where
     /// [`apply_changeset`]: Self::apply_changeset
     pub fn apply_update(
         &mut self,
-        scan: KeychainScan<K, P>,
-    ) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
+        scan: KeychainScan<K, A, P>,
+    ) -> Result<KeychainChangeSet<K, A, P>, chain_graph::UpdateError<P>> {
         let changeset = self.determine_changeset(&scan)?;
         self.apply_changeset(changeset.clone());
         Ok(changeset)
@@ -100,7 +101,7 @@ where
     ///
     /// Internally, this calls [`KeychainTxOutIndex::apply_additions`] and
     /// [`ChainGraph::apply_changeset`] in sequence.
-    pub fn apply_changeset(&mut self, changeset: KeychainChangeSet<K, P>) {
+    pub fn apply_changeset(&mut self, changeset: KeychainChangeSet<K, A, P>) {
         let KeychainChangeSet {
             derivation_indices,
             chain_graph,
@@ -132,12 +133,12 @@ where
     }
 
     /// Returns a reference to the internal [`ChainGraph`].
-    pub fn chain_graph(&self) -> &ChainGraph<P> {
+    pub fn chain_graph(&self) -> &ChainGraph<A, P> {
         &self.chain_graph
     }
 
     /// Returns a reference to the internal [`TxGraph`] (which is part of the [`ChainGraph`]).
-    pub fn graph(&self) -> &TxGraph {
+    pub fn graph(&self) -> &TxGraph<A> {
         self.chain_graph().graph()
     }
 
@@ -159,7 +160,7 @@ where
     pub fn insert_checkpoint_preview(
         &self,
         block_id: BlockId,
-    ) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertCheckpointError> {
+    ) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertCheckpointError> {
         Ok(KeychainChangeSet {
             chain_graph: self.chain_graph.insert_checkpoint_preview(block_id)?,
             ..Default::default()
@@ -176,7 +177,7 @@ where
     pub fn insert_checkpoint(
         &mut self,
         block_id: BlockId,
-    ) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertCheckpointError> {
+    ) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertCheckpointError> {
         let changeset = self.insert_checkpoint_preview(block_id)?;
         self.apply_changeset(changeset.clone());
         Ok(changeset)
@@ -191,7 +192,7 @@ where
         &self,
         tx: Transaction,
         pos: P,
-    ) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertTxError<P>> {
+    ) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertTxError<P>> {
         Ok(KeychainChangeSet {
             chain_graph: self.chain_graph.insert_tx_preview(tx, pos)?,
             ..Default::default()
@@ -209,7 +210,7 @@ where
         &mut self,
         tx: Transaction,
         pos: P,
-    ) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertTxError<P>> {
+    ) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertTxError<P>> {
         let changeset = self.insert_tx_preview(tx, pos)?;
         self.apply_changeset(changeset.clone());
         Ok(changeset)
@@ -280,7 +281,7 @@ where
     }
 }
 
-impl<K, P> Default for KeychainTracker<K, P> {
+impl<K, A, P> Default for KeychainTracker<K, A, P> {
     fn default() -> Self {
         Self {
             txout_index: Default::default(),
@@ -289,20 +290,20 @@ impl<K, P> Default for KeychainTracker<K, P> {
     }
 }
 
-impl<K, P> AsRef<SparseChain<P>> for KeychainTracker<K, P> {
+impl<K, A, P> AsRef<SparseChain<P>> for KeychainTracker<K, A, P> {
     fn as_ref(&self) -> &SparseChain<P> {
         self.chain_graph.chain()
     }
 }
 
-impl<K, P> AsRef<TxGraph> for KeychainTracker<K, P> {
-    fn as_ref(&self) -> &TxGraph {
+impl<K, A, P> AsRef<TxGraph<A>> for KeychainTracker<K, A, P> {
+    fn as_ref(&self) -> &TxGraph<A> {
         self.chain_graph.graph()
     }
 }
 
-impl<K, P> AsRef<ChainGraph<P>> for KeychainTracker<K, P> {
-    fn as_ref(&self) -> &ChainGraph<P> {
+impl<K, A, P> AsRef<ChainGraph<A, P>> for KeychainTracker<K, A, P> {
+    fn as_ref(&self) -> &ChainGraph<A, P> {
         &self.chain_graph
     }
 }
index b9c1e24ba8f1c149292608c58a3e7dcd949b4b1e..a449638d894ae4d954de93bcc9bd6bb57176d5b3 100644 (file)
@@ -899,7 +899,7 @@ impl<P: ChainPosition> SparseChain<P> {
     /// Attempt to retrieve a [`FullTxOut`] of the given `outpoint`.
     ///
     /// This will return `Some` only if the output's transaction is in both `self` and `graph`.
-    pub fn full_txout(&self, graph: &TxGraph, outpoint: OutPoint) -> Option<FullTxOut<P>> {
+    pub fn full_txout<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<FullTxOut<P>> {
         let chain_pos = self.tx_position(outpoint.txid)?;
 
         let tx = graph.get_tx(outpoint.txid)?;
@@ -972,7 +972,7 @@ impl<P: ChainPosition> SparseChain<P> {
     ///
     /// Note that the transaction including `outpoint` does not need to be in the `graph` or the
     /// `chain` for this to return `Some`.
-    pub fn spent_by(&self, graph: &TxGraph, outpoint: OutPoint) -> Option<(&P, Txid)> {
+    pub fn spent_by<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<(&P, Txid)> {
         graph
             .outspends(outpoint)
             .iter()
index 432592b82137a31298fd1bf106266be78cfe1e71..9b9facabeb936b6533098c48710f7e40db9f7c62 100644 (file)
@@ -1,4 +1,6 @@
-use bitcoin::{Block, OutPoint, Transaction, TxOut};
+use bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};
+
+use crate::BlockId;
 
 /// Trait to do something with every txout contained in a structure.
 ///
@@ -31,3 +33,19 @@ impl ForEachTxOut for Transaction {
         }
     }
 }
+
+/// Trait that "anchors" blockchain data in a specific block of height and hash.
+///
+/// This trait is typically associated with blockchain data such as transactions.
+pub trait BlockAnchor:
+    core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash + Send + Sync + 'static
+{
+    /// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.
+    fn anchor_block(&self) -> BlockId;
+}
+
+impl BlockAnchor for (u32, BlockHash) {
+    fn anchor_block(&self) -> BlockId {
+        (*self).into()
+    }
+}
index 3326ac4a2100d472c0bb33dd783c0c23179d6b7b..824b68e2077b197a59411e1f67fef02b9996e51e 100644 (file)
 //! of the changes to [`TxGraph`].
 //!
 //! ```
+//! # use bdk_chain::BlockId;
 //! # use bdk_chain::tx_graph::TxGraph;
 //! # use bdk_chain::example_utils::*;
 //! # use bitcoin::Transaction;
 //! # let tx_a = tx_from_hex(RAW_TX_1);
 //! # let tx_b = tx_from_hex(RAW_TX_2);
-//! let mut graph = TxGraph::default();
+//! let mut graph = TxGraph::<BlockId>::default();
 //!
 //! // preview a transaction insertion (not actually inserted)
 //! let additions = graph.insert_tx_preview(tx_a);
 //! A [`TxGraph`] can also be updated with another [`TxGraph`].
 //!
 //! ```
+//! # use bdk_chain::BlockId;
 //! # use bdk_chain::tx_graph::TxGraph;
 //! # use bdk_chain::example_utils::*;
 //! # use bitcoin::Transaction;
 //! # let tx_a = tx_from_hex(RAW_TX_1);
 //! # let tx_b = tx_from_hex(RAW_TX_2);
-//! let mut graph = TxGraph::default();
+//! let mut graph = TxGraph::<BlockId>::default();
 //! let update = TxGraph::new(vec![tx_a, tx_b]);
 //!
 //! // preview additions as the result of the update
 //! let additions = graph.apply_update(update);
 //! assert!(additions.is_empty());
 //! ```
-use crate::{collections::*, ForEachTxOut};
+
+use crate::{collections::*, BlockAnchor, BlockId, ForEachTxOut};
 use alloc::vec::Vec;
 use bitcoin::{OutPoint, Transaction, TxOut, Txid};
-use core::ops::RangeInclusive;
+use core::ops::{Deref, RangeInclusive};
 
 /// A graph of transactions and spends.
 ///
 /// See the [module-level documentation] for more.
 ///
 /// [module-level documentation]: crate::tx_graph
-#[derive(Clone, Debug, PartialEq, Default)]
-pub struct TxGraph {
-    txs: HashMap<Txid, TxNode>,
+#[derive(Clone, Debug, PartialEq)]
+pub struct TxGraph<A = BlockId> {
+    // all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
+    txs: HashMap<Txid, (TxNode, BTreeSet<A>, u64)>,
     spends: BTreeMap<OutPoint, HashSet<Txid>>,
+    anchors: BTreeSet<(A, Txid)>,
 
     // This atrocity exists so that `TxGraph::outspends()` can return a reference.
     // FIXME: This can be removed once `HashSet::new` is a const fn.
     empty_outspends: HashSet<Txid>,
 }
 
-/// Node of a [`TxGraph`]. This can either be a whole transaction, or a partial transaction (where
-/// we only have select outputs).
+impl<A> Default for TxGraph<A> {
+    fn default() -> Self {
+        Self {
+            txs: Default::default(),
+            spends: Default::default(),
+            anchors: Default::default(),
+            empty_outspends: Default::default(),
+        }
+    }
+}
+
+/// An outward-facing view of a transaction that resides in a [`TxGraph`].
+#[derive(Clone, Debug, PartialEq)]
+pub struct GraphedTx<'a, T, A> {
+    /// Txid of the transaction.
+    pub txid: Txid,
+    /// A partial or full representation of the transaction.
+    pub tx: &'a T,
+    /// The blocks that the transaction is "anchored" in.
+    pub anchors: &'a BTreeSet<A>,
+    /// The last-seen unix timestamp of the transaction.
+    pub last_seen: u64,
+}
+
+impl<'a, T, A> Deref for GraphedTx<'a, T, A> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.tx
+    }
+}
+
+impl<'a, A> GraphedTx<'a, Transaction, A> {
+    pub fn from_tx(tx: &'a Transaction, anchors: &'a BTreeSet<A>) -> Self {
+        Self {
+            txid: tx.txid(),
+            tx,
+            anchors,
+            last_seen: 0,
+        }
+    }
+}
+
+/// Internal representation of a transaction node of a [`TxGraph`].
+///
+/// This can either be a whole transaction, or a partial transaction (where we only have select
+/// outputs).
 #[derive(Clone, Debug, PartialEq)]
 enum TxNode {
     Whole(Transaction),
@@ -86,10 +136,10 @@ impl Default for TxNode {
     }
 }
 
-impl TxGraph {
+impl<A> TxGraph<A> {
     /// Iterate over all tx outputs known by [`TxGraph`].
     pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
-        self.txs.iter().flat_map(|(txid, tx)| match tx {
+        self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
             TxNode::Whole(tx) => tx
                 .output
                 .iter()
@@ -104,11 +154,18 @@ impl TxGraph {
     }
 
     /// Iterate over all full transactions in the graph.
-    pub fn full_transactions(&self) -> impl Iterator<Item = &Transaction> {
-        self.txs.iter().filter_map(|(_, tx)| match tx {
-            TxNode::Whole(tx) => Some(tx),
-            TxNode::Partial(_) => None,
-        })
+    pub fn full_transactions(&self) -> impl Iterator<Item = GraphedTx<'_, Transaction, A>> {
+        self.txs
+            .iter()
+            .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
+                TxNode::Whole(tx) => Some(GraphedTx {
+                    txid,
+                    tx,
+                    anchors,
+                    last_seen: *last_seen,
+                }),
+                TxNode::Partial(_) => None,
+            })
     }
 
     /// Get a transaction by txid. This only returns `Some` for full transactions.
@@ -116,16 +173,21 @@ impl TxGraph {
     /// Refer to [`get_txout`] for getting a specific [`TxOut`].
     ///
     /// [`get_txout`]: Self::get_txout
-    pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> {
-        match self.txs.get(&txid)? {
-            TxNode::Whole(tx) => Some(tx),
-            TxNode::Partial(_) => None,
+    pub fn get_tx(&self, txid: Txid) -> Option<GraphedTx<'_, Transaction, A>> {
+        match &self.txs.get(&txid)? {
+            (TxNode::Whole(tx), anchors, last_seen) => Some(GraphedTx {
+                txid,
+                tx,
+                anchors,
+                last_seen: *last_seen,
+            }),
+            _ => None,
         }
     }
 
     /// Obtains a single tx output (if any) at the specified outpoint.
     pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
-        match self.txs.get(&outpoint.txid)? {
+        match &self.txs.get(&outpoint.txid)?.0 {
             TxNode::Whole(tx) => tx.output.get(outpoint.vout as usize),
             TxNode::Partial(txouts) => txouts.get(&outpoint.vout),
         }
@@ -133,7 +195,7 @@ impl TxGraph {
 
     /// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
     pub fn txouts(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
-        Some(match self.txs.get(&txid)? {
+        Some(match &self.txs.get(&txid)?.0 {
             TxNode::Whole(tx) => tx
                 .output
                 .iter()
@@ -178,7 +240,7 @@ impl TxGraph {
     }
 }
 
-impl TxGraph {
+impl<A: BlockAnchor> TxGraph<A> {
     /// Construct a new [`TxGraph`] from a list of transactions.
     pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
         let mut new = Self::default();
@@ -187,11 +249,12 @@ impl TxGraph {
         }
         new
     }
+
     /// Inserts the given [`TxOut`] at [`OutPoint`].
     ///
     /// Note this will ignore the action if we already have the full transaction that the txout is
     /// alleged to be on (even if it doesn't match it!).
-    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions {
+    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
         let additions = self.insert_txout_preview(outpoint, txout);
         self.apply_additions(additions.clone());
         additions
@@ -200,25 +263,52 @@ impl TxGraph {
     /// Inserts the given transaction into [`TxGraph`].
     ///
     /// The [`Additions`] returned will be empty if `tx` already exists.
-    pub fn insert_tx(&mut self, tx: Transaction) -> Additions {
+    pub fn insert_tx(&mut self, tx: Transaction) -> Additions<A> {
         let additions = self.insert_tx_preview(tx);
         self.apply_additions(additions.clone());
         additions
     }
 
+    /// Inserts the given `anchor` into [`TxGraph`].
+    ///
+    /// This is equivalent to calling [`insert_anchor_preview`] and [`apply_additions`] in sequence.
+    /// The [`Additions`] returned will be empty if graph already knows that `txid` exists in
+    /// `anchor`.
+    ///
+    /// [`insert_anchor_preview`]: Self::insert_anchor_preview
+    /// [`apply_additions`]: Self::apply_additions
+    pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> Additions<A> {
+        let additions = self.insert_anchor_preview(txid, anchor);
+        self.apply_additions(additions.clone());
+        additions
+    }
+
+    /// Inserts the given `seen_at` into [`TxGraph`].
+    ///
+    /// This is equivalent to calling [`insert_seen_at_preview`] and [`apply_additions`] in
+    /// sequence.
+    ///
+    /// [`insert_seen_at_preview`]: Self::insert_seen_at_preview
+    /// [`apply_additions`]: Self::apply_additions
+    pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> Additions<A> {
+        let additions = self.insert_seen_at_preview(txid, seen_at);
+        self.apply_additions(additions.clone());
+        additions
+    }
+
     /// Extends this graph with another so that `self` becomes the union of the two sets of
     /// transactions.
     ///
     /// The returned [`Additions`] is the set difference between `update` and `self` (transactions that
     /// exist in `update` but not in `self`).
-    pub fn apply_update(&mut self, update: TxGraph) -> Additions {
+    pub fn apply_update(&mut self, update: TxGraph<A>) -> Additions<A> {
         let additions = self.determine_additions(&update);
         self.apply_additions(additions.clone());
         additions
     }
 
     /// Applies [`Additions`] to [`TxGraph`].
-    pub fn apply_additions(&mut self, additions: Additions) {
+    pub fn apply_additions(&mut self, additions: Additions<A>) {
         for tx in additions.tx {
             let txid = tx.txid();
 
@@ -232,12 +322,21 @@ impl TxGraph {
                     self.spends.entry(outpoint).or_default().insert(txid);
                 });
 
-            if let Some(TxNode::Whole(old_tx)) = self.txs.insert(txid, TxNode::Whole(tx)) {
-                debug_assert_eq!(
-                    old_tx.txid(),
-                    txid,
-                    "old tx of the same txid should not be different."
-                );
+            match self.txs.get_mut(&txid) {
+                Some((tx_node @ TxNode::Partial(_), _, _)) => {
+                    *tx_node = TxNode::Whole(tx);
+                }
+                Some((TxNode::Whole(tx), _, _)) => {
+                    debug_assert_eq!(
+                        tx.txid(),
+                        txid,
+                        "tx should produce txid that is same as key"
+                    );
+                }
+                None => {
+                    self.txs
+                        .insert(txid, (TxNode::Whole(tx), BTreeSet::new(), 0));
+                }
             }
         }
 
@@ -245,47 +344,75 @@ impl TxGraph {
             let tx_entry = self
                 .txs
                 .entry(outpoint.txid)
-                .or_insert_with(TxNode::default);
+                .or_insert_with(Default::default);
 
             match tx_entry {
-                TxNode::Whole(_) => { /* do nothing since we already have full tx */ }
-                TxNode::Partial(txouts) => {
+                (TxNode::Whole(_), _, _) => { /* do nothing since we already have full tx */ }
+                (TxNode::Partial(txouts), _, _) => {
                     txouts.insert(outpoint.vout, txout);
                 }
             }
         }
+
+        for (anchor, txid) in additions.anchors {
+            if self.anchors.insert((anchor.clone(), txid)) {
+                let (_, anchors, _) = self.txs.entry(txid).or_insert_with(Default::default);
+                anchors.insert(anchor);
+            }
+        }
+
+        for (txid, new_last_seen) in additions.last_seen {
+            let (_, _, last_seen) = self.txs.entry(txid).or_insert_with(Default::default);
+            if new_last_seen > *last_seen {
+                *last_seen = new_last_seen;
+            }
+        }
     }
 
     /// Previews the resultant [`Additions`] when [`Self`] is updated against the `update` graph.
     ///
     /// The [`Additions`] would be the set difference between `update` and `self` (transactions that
     /// exist in `update` but not in `self`).
-    pub fn determine_additions(&self, update: &TxGraph) -> Additions {
+    pub fn determine_additions(&self, update: &TxGraph<A>) -> Additions<A> {
         let mut additions = Additions::default();
 
-        for (&txid, update_tx) in &update.txs {
-            if self.get_tx(txid).is_some() {
-                continue;
-            }
-
-            match update_tx {
-                TxNode::Whole(tx) => {
-                    if matches!(self.txs.get(&txid), None | Some(TxNode::Partial(_))) {
-                        additions.tx.insert(tx.clone());
-                    }
+        for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
+            let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
+                (None, TxNode::Whole(update_tx)) => {
+                    additions.tx.insert(update_tx.clone());
+                    0
+                }
+                (None, TxNode::Partial(update_txos)) => {
+                    additions.txout.extend(
+                        update_txos
+                            .iter()
+                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
+                    );
+                    0
                 }
-                TxNode::Partial(partial) => {
-                    for (&vout, update_txout) in partial {
-                        let outpoint = OutPoint::new(txid, vout);
-
-                        if self.get_txout(outpoint) != Some(update_txout) {
-                            additions.txout.insert(outpoint, update_txout.clone());
-                        }
-                    }
+                (Some((TxNode::Whole(_), _, last_seen)), _) => *last_seen,
+                (Some((TxNode::Partial(_), _, last_seen)), TxNode::Whole(update_tx)) => {
+                    additions.tx.insert(update_tx.clone());
+                    *last_seen
                 }
+                (Some((TxNode::Partial(txos), _, last_seen)), TxNode::Partial(update_txos)) => {
+                    additions.txout.extend(
+                        update_txos
+                            .iter()
+                            .filter(|(vout, _)| !txos.contains_key(*vout))
+                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
+                    );
+                    *last_seen
+                }
+            };
+
+            if *update_last_seen > prev_last_seen {
+                additions.last_seen.insert(txid, *update_last_seen);
             }
         }
 
+        additions.anchors = update.anchors.difference(&self.anchors).cloned().collect();
+
         additions
     }
 
@@ -293,9 +420,11 @@ impl TxGraph {
     /// mutate [`Self`].
     ///
     /// The [`Additions`] result will be empty if `tx` already exists in `self`.
-    pub fn insert_tx_preview(&self, tx: Transaction) -> Additions {
+    pub fn insert_tx_preview(&self, tx: Transaction) -> Additions<A> {
         let mut update = Self::default();
-        update.txs.insert(tx.txid(), TxNode::Whole(tx));
+        update
+            .txs
+            .insert(tx.txid(), (TxNode::Whole(tx), BTreeSet::new(), 0));
         self.determine_additions(&update)
     }
 
@@ -304,17 +433,38 @@ impl TxGraph {
     ///
     /// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing
     /// the `outpoint`) already existed in `self`.
-    pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions {
+    pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
         let mut update = Self::default();
         update.txs.insert(
             outpoint.txid,
-            TxNode::Partial([(outpoint.vout, txout)].into()),
+            (
+                TxNode::Partial([(outpoint.vout, txout)].into()),
+                BTreeSet::new(),
+                0,
+            ),
         );
         self.determine_additions(&update)
     }
+
+    /// Returns the resultant [`Additions`] if the `txid` is set in `anchor`.
+    pub fn insert_anchor_preview(&self, txid: Txid, anchor: A) -> Additions<A> {
+        let mut update = Self::default();
+        update.anchors.insert((anchor, txid));
+        self.determine_additions(&update)
+    }
+
+    /// Returns the resultant [`Additions`] if the `txid` is set to `seen_at`.
+    ///
+    /// Note that [`TxGraph`] only keeps track of the lastest `seen_at`.
+    pub fn insert_seen_at_preview(&self, txid: Txid, seen_at: u64) -> Additions<A> {
+        let mut update = Self::default();
+        let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
+        *update_last_seen = seen_at;
+        self.determine_additions(&update)
+    }
 }
 
-impl TxGraph {
+impl<A> TxGraph<A> {
     /// The transactions spending from this output.
     ///
     /// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
@@ -344,11 +494,20 @@ impl TxGraph {
     }
 
     /// Iterate over all partial transactions (outputs only) in the graph.
-    pub fn partial_transactions(&self) -> impl Iterator<Item = (Txid, &BTreeMap<u32, TxOut>)> {
-        self.txs.iter().filter_map(|(txid, tx)| match tx {
-            TxNode::Whole(_) => None,
-            TxNode::Partial(partial) => Some((*txid, partial)),
-        })
+    pub fn partial_transactions(
+        &self,
+    ) -> impl Iterator<Item = GraphedTx<'_, BTreeMap<u32, TxOut>, A>> {
+        self.txs
+            .iter()
+            .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
+                TxNode::Whole(_) => None,
+                TxNode::Partial(partial) => Some(GraphedTx {
+                    txid,
+                    tx: partial,
+                    anchors,
+                    last_seen: *last_seen,
+                }),
+            })
     }
 
     /// Creates an iterator that filters and maps descendants from the starting `txid`.
@@ -361,7 +520,7 @@ impl TxGraph {
     ///
     /// The supplied closure returns an `Option<T>`, allowing the caller to map each node it vists
     /// and decide whether to visit descendants.
-    pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<F>
+    pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<A, F>
     where
         F: FnMut(usize, Txid) -> Option<O> + 'g,
     {
@@ -372,7 +531,11 @@ impl TxGraph {
     /// descendants of directly-conflicting transactions, which are also considered conflicts).
     ///
     /// Refer to [`Self::walk_descendants`] for `walk_map` usage.
-    pub fn walk_conflicts<'g, F, O>(&'g self, tx: &'g Transaction, walk_map: F) -> TxDescendants<F>
+    pub fn walk_conflicts<'g, F, O>(
+        &'g self,
+        tx: &'g Transaction,
+        walk_map: F,
+    ) -> TxDescendants<A, F>
     where
         F: FnMut(usize, Txid) -> Option<O> + 'g,
     {
@@ -413,19 +576,38 @@ impl TxGraph {
 /// Refer to [module-level documentation] for more.
 ///
 /// [module-level documentation]: crate::tx_graph
-#[derive(Debug, Clone, PartialEq, Default)]
+#[derive(Debug, Clone, PartialEq)]
 #[cfg_attr(
     feature = "serde",
     derive(serde::Deserialize, serde::Serialize),
-    serde(crate = "serde_crate")
+    serde(
+        crate = "serde_crate",
+        bound(
+            deserialize = "A: Ord + serde::Deserialize<'de>",
+            serialize = "A: Ord + serde::Serialize",
+        )
+    )
 )]
 #[must_use]
-pub struct Additions {
+pub struct Additions<A = BlockId> {
     pub tx: BTreeSet<Transaction>,
     pub txout: BTreeMap<OutPoint, TxOut>,
+    pub anchors: BTreeSet<(A, Txid)>,
+    pub last_seen: BTreeMap<Txid, u64>,
+}
+
+impl<A> Default for Additions<A> {
+    fn default() -> Self {
+        Self {
+            tx: Default::default(),
+            txout: Default::default(),
+            anchors: Default::default(),
+            last_seen: Default::default(),
+        }
+    }
 }
 
-impl Additions {
+impl<A> Additions<A> {
     /// Returns true if the [`Additions`] is empty (no transactions or txouts).
     pub fn is_empty(&self) -> bool {
         self.tx.is_empty() && self.txout.is_empty()
@@ -446,25 +628,25 @@ impl Additions {
 
     /// Appends the changes in `other` into self such that applying `self` afterward has the same
     /// effect as sequentially applying the original `self` and `other`.
-    pub fn append(&mut self, mut other: Additions) {
+    pub fn append(&mut self, mut other: Additions<A>) {
         self.tx.append(&mut other.tx);
         self.txout.append(&mut other.txout);
     }
 }
 
-impl AsRef<TxGraph> for TxGraph {
-    fn as_ref(&self) -> &TxGraph {
+impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
+    fn as_ref(&self) -> &TxGraph<A> {
         self
     }
 }
 
-impl ForEachTxOut for Additions {
+impl<A> ForEachTxOut for Additions<A> {
     fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
         self.txouts().for_each(f)
     }
 }
 
-impl ForEachTxOut for TxGraph {
+impl<A> ForEachTxOut for TxGraph<A> {
     fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
         self.all_txouts().for_each(f)
     }
@@ -475,17 +657,17 @@ impl ForEachTxOut for TxGraph {
 /// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
 ///
 /// [`walk_descendants`]: TxGraph::walk_descendants
-pub struct TxDescendants<'g, F> {
-    graph: &'g TxGraph,
+pub struct TxDescendants<'g, A, F> {
+    graph: &'g TxGraph<A>,
     visited: HashSet<Txid>,
     stack: Vec<(usize, Txid)>,
     filter_map: F,
 }
 
-impl<'g, F> TxDescendants<'g, F> {
+impl<'g, A, F> TxDescendants<'g, A, F> {
     /// Creates a `TxDescendants` that includes the starting `txid` when iterating.
     #[allow(unused)]
-    pub(crate) fn new_include_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self {
+    pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
         Self {
             graph,
             visited: Default::default(),
@@ -495,7 +677,7 @@ impl<'g, F> TxDescendants<'g, F> {
     }
 
     /// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
-    pub(crate) fn new_exclude_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self {
+    pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
         let mut descendants = Self {
             graph,
             visited: Default::default(),
@@ -508,7 +690,11 @@ impl<'g, F> TxDescendants<'g, F> {
 
     /// Creates a `TxDescendants` from multiple starting transactions that include the starting
     /// `txid`s when iterating.
-    pub(crate) fn from_multiple_include_root<I>(graph: &'g TxGraph, txids: I, filter_map: F) -> Self
+    pub(crate) fn from_multiple_include_root<I>(
+        graph: &'g TxGraph<A>,
+        txids: I,
+        filter_map: F,
+    ) -> Self
     where
         I: IntoIterator<Item = Txid>,
     {
@@ -523,7 +709,11 @@ impl<'g, F> TxDescendants<'g, F> {
     /// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
     /// `txid`s when iterating.
     #[allow(unused)]
-    pub(crate) fn from_multiple_exclude_root<I>(graph: &'g TxGraph, txids: I, filter_map: F) -> Self
+    pub(crate) fn from_multiple_exclude_root<I>(
+        graph: &'g TxGraph<A>,
+        txids: I,
+        filter_map: F,
+    ) -> Self
     where
         I: IntoIterator<Item = Txid>,
     {
@@ -540,7 +730,7 @@ impl<'g, F> TxDescendants<'g, F> {
     }
 }
 
-impl<'g, F> TxDescendants<'g, F> {
+impl<'g, A, F> TxDescendants<'g, A, F> {
     fn populate_stack(&mut self, depth: usize, txid: Txid) {
         let spend_paths = self
             .graph
@@ -552,7 +742,7 @@ impl<'g, F> TxDescendants<'g, F> {
     }
 }
 
-impl<'g, F, O> Iterator for TxDescendants<'g, F>
+impl<'g, A, F, O> Iterator for TxDescendants<'g, A, F>
 where
     F: FnMut(usize, Txid) -> Option<O>,
 {
index 68f50b8f7ab75d4ec87889d94e95101a109a0f13..cd2a289436b6d8cb35652922e0a42b8d5ead68a7 100644 (file)
@@ -1,14 +1,18 @@
 #[macro_use]
 mod common;
 
+use std::collections::BTreeSet;
+
 use bdk_chain::{
     chain_graph::*,
     collections::HashSet,
     sparse_chain,
-    tx_graph::{self, TxGraph},
+    tx_graph::{self, GraphedTx, TxGraph},
     BlockId, TxHeight,
 };
-use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness};
+use bitcoin::{
+    BlockHash, OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness,
+};
 
 #[test]
 fn test_spent_by() {
@@ -43,7 +47,7 @@ fn test_spent_by() {
         output: vec![],
     };
 
-    let mut cg1 = ChainGraph::default();
+    let mut cg1 = ChainGraph::<(u32, BlockHash), _>::default();
     let _ = cg1
         .insert_tx(tx1, TxHeight::Unconfirmed)
         .expect("should insert");
@@ -124,7 +128,7 @@ fn update_evicts_conflicting_tx() {
             cg
         };
 
-        let changeset = ChangeSet::<TxHeight> {
+        let changeset = ChangeSet::<(u32, BlockHash), TxHeight> {
             chain: sparse_chain::ChangeSet {
                 checkpoints: Default::default(),
                 txids: [
@@ -133,9 +137,10 @@ fn update_evicts_conflicting_tx() {
                 ]
                 .into(),
             },
-            graph: tx_graph::Additions {
+            graph: tx_graph::Additions::<(u32, BlockHash)> {
                 tx: [tx_b2.clone()].into(),
                 txout: [].into(),
+                ..Default::default()
             },
         };
         assert_eq!(
@@ -149,7 +154,7 @@ fn update_evicts_conflicting_tx() {
 
     {
         let cg1 = {
-            let mut cg = ChainGraph::default();
+            let mut cg = ChainGraph::<(u32, BlockHash), _>::default();
             let _ = cg.insert_checkpoint(cp_a).expect("should insert cp");
             let _ = cg.insert_checkpoint(cp_b).expect("should insert cp");
             let _ = cg
@@ -203,7 +208,7 @@ fn update_evicts_conflicting_tx() {
             cg
         };
 
-        let changeset = ChangeSet::<TxHeight> {
+        let changeset = ChangeSet::<(u32, BlockHash), TxHeight> {
             chain: sparse_chain::ChangeSet {
                 checkpoints: [(1, Some(h!("B'")))].into(),
                 txids: [
@@ -212,9 +217,10 @@ fn update_evicts_conflicting_tx() {
                 ]
                 .into(),
             },
-            graph: tx_graph::Additions {
+            graph: tx_graph::Additions::<(u32, BlockHash)> {
                 tx: [tx_b2].into(),
                 txout: [].into(),
+                ..Default::default()
             },
         };
         assert_eq!(
@@ -250,7 +256,7 @@ fn chain_graph_new_missing() {
             (tx_b.txid(), TxHeight::Confirmed(0))
         ]
     );
-    let mut graph = TxGraph::default();
+    let mut graph = TxGraph::<(u32, BlockHash)>::default();
 
     let mut expected_missing = HashSet::new();
     expected_missing.insert(tx_a.txid());
@@ -287,7 +293,7 @@ fn chain_graph_new_missing() {
 
     let new_graph = ChainGraph::new(update.clone(), graph.clone()).unwrap();
     let expected_graph = {
-        let mut cg = ChainGraph::<TxHeight>::default();
+        let mut cg = ChainGraph::<(u32, BlockHash), TxHeight>::default();
         let _ = cg
             .insert_checkpoint(update.latest_checkpoint().unwrap())
             .unwrap();
@@ -342,7 +348,7 @@ fn chain_graph_new_conflicts() {
         ]
     );
 
-    let graph = TxGraph::new([tx_a, tx_b, tx_b2]);
+    let graph = TxGraph::<(u32, BlockHash)>::new([tx_a, tx_b, tx_b2]);
 
     assert!(matches!(
         ChainGraph::new(chain, graph),
@@ -352,7 +358,7 @@ fn chain_graph_new_conflicts() {
 
 #[test]
 fn test_get_tx_in_chain() {
-    let mut cg = ChainGraph::default();
+    let mut cg = ChainGraph::<(u32, BlockHash), _>::default();
     let tx = Transaction {
         version: 0x01,
         lock_time: PackedLockTime(0),
@@ -363,13 +369,21 @@ fn test_get_tx_in_chain() {
     let _ = cg.insert_tx(tx.clone(), TxHeight::Unconfirmed).unwrap();
     assert_eq!(
         cg.get_tx_in_chain(tx.txid()),
-        Some((&TxHeight::Unconfirmed, &tx))
+        Some((
+            &TxHeight::Unconfirmed,
+            GraphedTx {
+                txid: tx.txid(),
+                tx: &tx,
+                anchors: &BTreeSet::new(),
+                last_seen: 0
+            }
+        ))
     );
 }
 
 #[test]
 fn test_iterate_transactions() {
-    let mut cg = ChainGraph::default();
+    let mut cg = ChainGraph::<BlockId, _>::default();
     let txs = (0..3)
         .map(|i| Transaction {
             version: i,
@@ -395,9 +409,18 @@ fn test_iterate_transactions() {
     assert_eq!(
         cg.transactions_in_chain().collect::<Vec<_>>(),
         vec![
-            (&TxHeight::Confirmed(0), &txs[2]),
-            (&TxHeight::Confirmed(1), &txs[0]),
-            (&TxHeight::Unconfirmed, &txs[1]),
+            (
+                &TxHeight::Confirmed(0),
+                GraphedTx::from_tx(&txs[2], &BTreeSet::new())
+            ),
+            (
+                &TxHeight::Confirmed(1),
+                GraphedTx::from_tx(&txs[0], &BTreeSet::new())
+            ),
+            (
+                &TxHeight::Unconfirmed,
+                GraphedTx::from_tx(&txs[1], &BTreeSet::new())
+            ),
         ]
     );
 }
@@ -457,7 +480,7 @@ fn test_apply_changes_reintroduce_tx() {
 
     // block1, block2a, tx1, tx2a
     let mut cg = {
-        let mut cg = ChainGraph::default();
+        let mut cg = ChainGraph::<(u32, BlockHash), _>::default();
         let _ = cg.insert_checkpoint(block1).unwrap();
         let _ = cg.insert_checkpoint(block2a).unwrap();
         let _ = cg.insert_tx(tx1, TxHeight::Confirmed(1)).unwrap();
@@ -613,7 +636,7 @@ fn test_evict_descendants() {
     let txid_conflict = tx_conflict.txid();
 
     let cg = {
-        let mut cg = ChainGraph::<TxHeight>::default();
+        let mut cg = ChainGraph::<(u32, BlockHash), TxHeight>::default();
         let _ = cg.insert_checkpoint(block_1);
         let _ = cg.insert_checkpoint(block_2a);
         let _ = cg.insert_tx(tx_1, TxHeight::Confirmed(1));
@@ -625,7 +648,7 @@ fn test_evict_descendants() {
     };
 
     let update = {
-        let mut cg = ChainGraph::<TxHeight>::default();
+        let mut cg = ChainGraph::<(u32, BlockHash), TxHeight>::default();
         let _ = cg.insert_checkpoint(block_1);
         let _ = cg.insert_checkpoint(block_2b);
         let _ = cg.insert_tx(tx_conflict.clone(), TxHeight::Confirmed(2));
index 3bf0a1d50f16e0a6466c48c77efad14cc72ca276..1c5e07956033bcb1c1c012718dba88ee392aa859 100644 (file)
@@ -1,19 +1,22 @@
 #![cfg(feature = "miniscript")]
 #[macro_use]
 mod common;
+use std::collections::BTreeSet;
+
 use bdk_chain::{
     keychain::{Balance, KeychainTracker},
     miniscript::{
         bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut},
         Descriptor,
     },
+    tx_graph::GraphedTx,
     BlockId, ConfirmationTime, TxHeight,
 };
-use bitcoin::TxIn;
+use bitcoin::{BlockHash, TxIn};
 
 #[test]
 fn test_insert_tx() {
-    let mut tracker = KeychainTracker::default();
+    let mut tracker = KeychainTracker::<_, BlockId, _>::default();
     let secp = Secp256k1::new();
     let (descriptor, _) = Descriptor::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
     tracker.add_keychain((), descriptor.clone());
@@ -40,7 +43,10 @@ fn test_insert_tx() {
             .chain_graph()
             .transactions_in_chain()
             .collect::<Vec<_>>(),
-        vec![(&ConfirmationTime::Unconfirmed, &tx)]
+        vec![(
+            &ConfirmationTime::Unconfirmed,
+            GraphedTx::from_tx(&tx, &BTreeSet::new())
+        )]
     );
 
     assert_eq!(
@@ -66,7 +72,7 @@ fn test_balance() {
         One,
         Two,
     }
-    let mut tracker = KeychainTracker::<Keychain, TxHeight>::default();
+    let mut tracker = KeychainTracker::<Keychain, (u32, BlockHash), TxHeight>::default();
     let one = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)#rg247h69").unwrap();
     let two = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/1/*)#ju05rz2a").unwrap();
     tracker.add_keychain(Keychain::One, one);
index 04974bf3092f392a0aba19ab168614f19973d364..2550d556848fc8bd0cd0ce92378f7ac78aa85153 100644 (file)
@@ -2,9 +2,12 @@
 mod common;
 use bdk_chain::{
     collections::*,
-    tx_graph::{Additions, TxGraph},
+    tx_graph::{Additions, GraphedTx, TxGraph},
+    BlockId,
+};
+use bitcoin::{
+    hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
 };
-use bitcoin::{hashes::Hash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid};
 use core::iter;
 
 #[test]
@@ -35,7 +38,7 @@ fn insert_txouts() {
     )];
 
     let mut graph = {
-        let mut graph = TxGraph::default();
+        let mut graph = TxGraph::<(u32, BlockHash)>::default();
         for (outpoint, txout) in &original_ops {
             assert_eq!(
                 graph.insert_txout(*outpoint, txout.clone()),
@@ -69,6 +72,7 @@ fn insert_txouts() {
         Additions {
             tx: [].into(),
             txout: update_ops.into(),
+            ..Default::default()
         }
     );
 
@@ -90,7 +94,7 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
         output: vec![],
     };
 
-    let mut graph = TxGraph::default();
+    let mut graph = TxGraph::<(u32, BlockHash)>::default();
     let _ = graph.insert_tx(tx);
     assert!(graph.outspends(OutPoint::null()).is_empty());
     assert!(graph.tx_outspends(Txid::all_zeros()).next().is_none());
@@ -120,8 +124,8 @@ fn insert_tx_graph_keeps_track_of_spend() {
         output: vec![],
     };
 
-    let mut graph1 = TxGraph::default();
-    let mut graph2 = TxGraph::default();
+    let mut graph1 = TxGraph::<(u32, BlockHash)>::default();
+    let mut graph2 = TxGraph::<(u32, BlockHash)>::default();
 
     // insert in different order
     let _ = graph1.insert_tx(tx1.clone());
@@ -149,14 +153,17 @@ fn insert_tx_can_retrieve_full_tx_from_graph() {
         output: vec![TxOut::default()],
     };
 
-    let mut graph = TxGraph::default();
+    let mut graph = TxGraph::<BlockId>::default();
     let _ = graph.insert_tx(tx.clone());
-    assert_eq!(graph.get_tx(tx.txid()), Some(&tx));
+    assert_eq!(
+        graph.get_tx(tx.txid()),
+        Some(GraphedTx::from_tx(&tx, &BTreeSet::new()))
+    );
 }
 
 #[test]
 fn insert_tx_displaces_txouts() {
-    let mut tx_graph = TxGraph::default();
+    let mut tx_graph = TxGraph::<(u32, BlockHash)>::default();
     let tx = Transaction {
         version: 0x01,
         lock_time: PackedLockTime(0),
@@ -212,7 +219,7 @@ fn insert_tx_displaces_txouts() {
 
 #[test]
 fn insert_txout_does_not_displace_tx() {
-    let mut tx_graph = TxGraph::default();
+    let mut tx_graph = TxGraph::<(u32, BlockHash)>::default();
     let tx = Transaction {
         version: 0x01,
         lock_time: PackedLockTime(0),
@@ -268,7 +275,7 @@ fn insert_txout_does_not_displace_tx() {
 
 #[test]
 fn test_calculate_fee() {
-    let mut graph = TxGraph::default();
+    let mut graph = TxGraph::<(u32, BlockHash)>::default();
     let intx1 = Transaction {
         version: 0x01,
         lock_time: PackedLockTime(0),
@@ -362,7 +369,7 @@ fn test_calculate_fee_on_coinbase() {
         output: vec![TxOut::default()],
     };
 
-    let graph = TxGraph::default();
+    let graph = TxGraph::<(u32, BlockHash)>::default();
 
     assert_eq!(graph.calculate_fee(&tx), Some(0));
 }
@@ -404,7 +411,7 @@ fn test_conflicting_descendants() {
     let txid_a = tx_a.txid();
     let txid_b = tx_b.txid();
 
-    let mut graph = TxGraph::default();
+    let mut graph = TxGraph::<(u32, BlockHash)>::default();
     let _ = graph.insert_tx(tx_a);
     let _ = graph.insert_tx(tx_b);
 
@@ -480,7 +487,7 @@ fn test_descendants_no_repeat() {
         })
         .collect::<Vec<_>>();
 
-    let mut graph = TxGraph::default();
+    let mut graph = TxGraph::<(u32, BlockHash)>::default();
     let mut expected_txids = BTreeSet::new();
 
     // these are NOT descendants of `tx_a`
index bddbd8f25286d9f6bd1b3fbf1cd7f14830e91946..d062cfdc3d8de7f894d73c4b33d5c703ef500a01 100644 (file)
@@ -32,7 +32,7 @@ use bdk_chain::{
     keychain::KeychainScan,
     sparse_chain::{self, ChainPosition, SparseChain},
     tx_graph::TxGraph,
-    BlockId, ConfirmationTime, TxHeight,
+    BlockAnchor, BlockId, ConfirmationTime, TxHeight,
 };
 pub use electrum_client;
 use electrum_client::{Client, ElectrumApi, Error};
@@ -243,13 +243,14 @@ impl<K: Ord + Clone + Debug, P: ChainPosition> ElectrumUpdate<K, P> {
     /// `tracker`.
     ///
     /// This will fail if there are missing full transactions not provided via `new_txs`.
-    pub fn into_keychain_scan<CG>(
+    pub fn into_keychain_scan<CG, A>(
         self,
         new_txs: Vec<Transaction>,
         chain_graph: &CG,
-    ) -> Result<KeychainScan<K, P>, chain_graph::NewError<P>>
+    ) -> Result<KeychainScan<K, A, P>, chain_graph::NewError<P>>
     where
-        CG: AsRef<ChainGraph<P>>,
+        CG: AsRef<ChainGraph<A, P>>,
+        A: BlockAnchor,
     {
         Ok(KeychainScan {
             update: chain_graph
index 266fd30b69c20b451d6867253f4c59f85939e201..420f1197ad4c2c8c74b99343d8bec06890022edd 100644 (file)
@@ -48,7 +48,7 @@ pub trait EsploraAsyncExt {
         outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<KeychainScan<K, ConfirmationTime>, Error>;
+    ) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error>;
 
     /// Convenience method to call [`scan`] without requiring a keychain.
     ///
@@ -61,7 +61,7 @@ pub trait EsploraAsyncExt {
         txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
         outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
         parallel_requests: usize,
-    ) -> Result<ChainGraph<ConfirmationTime>, Error> {
+    ) -> Result<ChainGraph<BlockId, ConfirmationTime>, Error> {
         let wallet_scan = self
             .scan(
                 local_chain,
@@ -100,7 +100,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
         outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<KeychainScan<K, ConfirmationTime>, Error> {
+    ) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error> {
         let txids = txids.into_iter();
         let outpoints = outpoints.into_iter();
         let parallel_requests = parallel_requests.max(1);
index c22668a530aecebe64789e19e2f83d8b32afdb91..d4a511ac77edb13945ff29dc72b71522ba5ec90c 100644 (file)
@@ -38,7 +38,7 @@ pub trait EsploraExt {
         outpoints: impl IntoIterator<Item = OutPoint>,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<KeychainScan<K, ConfirmationTime>, Error>;
+    ) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error>;
 
     /// Convenience method to call [`scan`] without requiring a keychain.
     ///
@@ -51,7 +51,7 @@ pub trait EsploraExt {
         txids: impl IntoIterator<Item = Txid>,
         outpoints: impl IntoIterator<Item = OutPoint>,
         parallel_requests: usize,
-    ) -> Result<ChainGraph<ConfirmationTime>, Error> {
+    ) -> Result<ChainGraph<BlockId, ConfirmationTime>, Error> {
         let wallet_scan = self.scan(
             local_chain,
             [(
@@ -81,7 +81,7 @@ impl EsploraExt for esplora_client::BlockingClient {
         outpoints: impl IntoIterator<Item = OutPoint>,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<KeychainScan<K, ConfirmationTime>, Error> {
+    ) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error> {
         let parallel_requests = parallel_requests.max(1);
         let mut scan = KeychainScan::default();
         let update = &mut scan.update;
index 824e3ccc56f94dbaa58ffc1ae88085434aa0e78f..ba0dc21dbe64a3b2e9c7c1ca3319ed66e4c0b0bd 100644 (file)
@@ -4,7 +4,7 @@
 //! [`KeychainChangeSet`]s which can be used to restore a [`KeychainTracker`].
 use bdk_chain::{
     keychain::{KeychainChangeSet, KeychainTracker},
-    sparse_chain,
+    sparse_chain, BlockAnchor,
 };
 use bincode::{DefaultOptions, Options};
 use core::marker::PhantomData;
@@ -23,20 +23,21 @@ const MAGIC_BYTES: [u8; MAGIC_BYTES_LEN] = [98, 100, 107, 102, 115, 48, 48, 48,
 /// Persists an append only list of `KeychainChangeSet<K,P>` to a single file.
 /// [`KeychainChangeSet<K,P>`] record the changes made to a [`KeychainTracker<K,P>`].
 #[derive(Debug)]
-pub struct KeychainStore<K, P> {
+pub struct KeychainStore<K, A, P> {
     db_file: File,
-    changeset_type_params: core::marker::PhantomData<(K, P)>,
+    changeset_type_params: core::marker::PhantomData<(K, A, P)>,
 }
 
 fn bincode() -> impl bincode::Options {
     DefaultOptions::new().with_varint_encoding()
 }
 
-impl<K, P> KeychainStore<K, P>
+impl<K, A, P> KeychainStore<K, A, P>
 where
     K: Ord + Clone + core::fmt::Debug,
+    A: BlockAnchor,
     P: sparse_chain::ChainPosition,
-    KeychainChangeSet<K, P>: serde::Serialize + serde::de::DeserializeOwned,
+    KeychainChangeSet<K, A, P>: serde::Serialize + serde::de::DeserializeOwned,
 {
     /// Creates a new store from a [`File`].
     ///
@@ -85,7 +86,9 @@ where
     /// **WARNING**: This method changes the write position in the underlying file. You should
     /// always iterate over all entries until `None` is returned if you want your next write to go
     /// at the end; otherwise, you will write over existing entries.
-    pub fn iter_changesets(&mut self) -> Result<EntryIter<'_, KeychainChangeSet<K, P>>, io::Error> {
+    pub fn iter_changesets(
+        &mut self,
+    ) -> Result<EntryIter<'_, KeychainChangeSet<K, A, P>>, io::Error> {
         self.db_file
             .seek(io::SeekFrom::Start(MAGIC_BYTES_LEN as _))?;
 
@@ -104,7 +107,7 @@ where
     ///
     /// **WARNING**: This method changes the write position of the underlying file. The next
     /// changeset will be written over the erroring entry (or the end of the file if none existed).
-    pub fn aggregate_changeset(&mut self) -> (KeychainChangeSet<K, P>, Result<(), IterError>) {
+    pub fn aggregate_changeset(&mut self) -> (KeychainChangeSet<K, A, P>, Result<(), IterError>) {
         let mut changeset = KeychainChangeSet::default();
         let result = (|| {
             let iter_changeset = self.iter_changesets()?;
@@ -124,7 +127,7 @@ where
     /// changeset will be written over the erroring entry (or the end of the file if none existed).
     pub fn load_into_keychain_tracker(
         &mut self,
-        tracker: &mut KeychainTracker<K, P>,
+        tracker: &mut KeychainTracker<K, A, P>,
     ) -> Result<(), IterError> {
         for changeset in self.iter_changesets()? {
             tracker.apply_changeset(changeset?)
@@ -138,7 +141,7 @@ where
     /// directly after the appended changeset.
     pub fn append_changeset(
         &mut self,
-        changeset: &KeychainChangeSet<K, P>,
+        changeset: &KeychainChangeSet<K, A, P>,
     ) -> Result<(), io::Error> {
         if changeset.is_empty() {
             return Ok(());
@@ -288,7 +291,7 @@ mod test {
     use super::*;
     use bdk_chain::{
         keychain::{DerivationAdditions, KeychainChangeSet},
-        TxHeight,
+        BlockId, TxHeight,
     };
     use std::{
         io::{Read, Write},
@@ -332,7 +335,7 @@ mod test {
         file.write_all(&MAGIC_BYTES[..MAGIC_BYTES_LEN - 1])
             .expect("should write");
 
-        match KeychainStore::<TestKeychain, TxHeight>::new(file.reopen().unwrap()) {
+        match KeychainStore::<TestKeychain, BlockId, TxHeight>::new(file.reopen().unwrap()) {
             Err(FileError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof),
             unexpected => panic!("unexpected result: {:?}", unexpected),
         };
@@ -346,7 +349,7 @@ mod test {
         file.write_all(invalid_magic_bytes.as_bytes())
             .expect("should write");
 
-        match KeychainStore::<TestKeychain, TxHeight>::new(file.reopen().unwrap()) {
+        match KeychainStore::<TestKeychain, BlockId, TxHeight>::new(file.reopen().unwrap()) {
             Err(FileError::InvalidMagicBytes(b)) => {
                 assert_eq!(b, invalid_magic_bytes.as_bytes())
             }
@@ -370,8 +373,9 @@ mod test {
         let mut file = NamedTempFile::new().unwrap();
         file.write_all(&data).expect("should write");
 
-        let mut store = KeychainStore::<TestKeychain, TxHeight>::new(file.reopen().unwrap())
-            .expect("should open");
+        let mut store =
+            KeychainStore::<TestKeychain, BlockId, TxHeight>::new(file.reopen().unwrap())
+                .expect("should open");
         match store.iter_changesets().expect("seek should succeed").next() {
             Some(Err(IterError::Bincode(_))) => {}
             unexpected_res => panic!("unexpected result: {:?}", unexpected_res),
index e334741947221ab81d9d32788bbf6d3c3c94aa08..a9673be94742ae0a62de411d1276897ccbaab81d 100644 (file)
@@ -3,14 +3,16 @@ mod file_store;
 use bdk_chain::{
     keychain::{KeychainChangeSet, KeychainTracker, PersistBackend},
     sparse_chain::ChainPosition,
+    BlockAnchor,
 };
 pub use file_store::*;
 
-impl<K, P> PersistBackend<K, P> for KeychainStore<K, P>
+impl<K, A, P> PersistBackend<K, A, P> for KeychainStore<K, A, P>
 where
     K: Ord + Clone + core::fmt::Debug,
+    A: BlockAnchor,
     P: ChainPosition,
-    KeychainChangeSet<K, P>: serde::Serialize + serde::de::DeserializeOwned,
+    KeychainChangeSet<K, A, P>: serde::Serialize + serde::de::DeserializeOwned,
 {
     type WriteError = std::io::Error;
 
@@ -18,14 +20,14 @@ where
 
     fn append_changeset(
         &mut self,
-        changeset: &KeychainChangeSet<K, P>,
+        changeset: &KeychainChangeSet<K, A, P>,
     ) -> Result<(), Self::WriteError> {
         KeychainStore::append_changeset(self, changeset)
     }
 
     fn load_into_keychain_tracker(
         &mut self,
-        tracker: &mut KeychainTracker<K, P>,
+        tracker: &mut KeychainTracker<K, A, P>,
     ) -> Result<(), Self::LoadError> {
         KeychainStore::load_into_keychain_tracker(self, tracker)
     }
index c8b9e0684db687724330e752dbd6c397e0ea1ffc..08f29ceb7cb94c5545d7ff50d42bf66cef185946 100644 (file)
@@ -48,7 +48,7 @@ pub struct ScanOptions {
 }
 
 fn main() -> anyhow::Result<()> {
-    let (args, keymap, tracker, db) = cli::init::<ElectrumCommands, _>()?;
+    let (args, keymap, tracker, db) = cli::init::<ElectrumCommands, _, _>()?;
 
     let electrum_url = match args.network {
         Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
index cae5e9601250cfa87ae83e8a4d6efec20baaf19a..04d121d23887887fd6c0e166d7e63fc8a604dca3 100644 (file)
@@ -49,7 +49,7 @@ pub struct ScanOptions {
 }
 
 fn main() -> anyhow::Result<()> {
-    let (args, keymap, keychain_tracker, db) = cli::init::<EsploraCommands, _>()?;
+    let (args, keymap, keychain_tracker, db) = cli::init::<EsploraCommands, _, _>()?;
     let esplora_url = match args.network {
         Network::Bitcoin => "https://mempool.space/api",
         Network::Testnet => "https://mempool.space/testnet/api",
index df42df1ac0691ca1049d06d85d3c487cc68f2d78..e118cbf43aee89676166db7eec0706c2171dc776 100644 (file)
@@ -13,7 +13,7 @@ use bdk_chain::{
         Descriptor, DescriptorPublicKey,
     },
     sparse_chain::{self, ChainPosition},
-    DescriptorExt, FullTxOut,
+    BlockAnchor, DescriptorExt, FullTxOut,
 };
 use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
 use bdk_file_store::KeychainStore;
@@ -179,15 +179,16 @@ pub struct AddrsOutput {
     used: bool,
 }
 
-pub fn run_address_cmd<P>(
-    tracker: &Mutex<KeychainTracker<Keychain, P>>,
-    db: &Mutex<KeychainStore<Keychain, P>>,
+pub fn run_address_cmd<A, P>(
+    tracker: &Mutex<KeychainTracker<Keychain, A, P>>,
+    db: &Mutex<KeychainStore<Keychain, A, P>>,
     addr_cmd: AddressCmd,
     network: Network,
 ) -> Result<()>
 where
+    A: bdk_chain::BlockAnchor,
     P: bdk_chain::sparse_chain::ChainPosition,
-    KeychainChangeSet<Keychain, P>: serde::Serialize + serde::de::DeserializeOwned,
+    KeychainChangeSet<Keychain, A, P>: serde::Serialize + serde::de::DeserializeOwned,
 {
     let mut tracker = tracker.lock().unwrap();
     let txout_index = &mut tracker.txout_index;
@@ -241,7 +242,9 @@ where
     }
 }
 
-pub fn run_balance_cmd<P: ChainPosition>(tracker: &Mutex<KeychainTracker<Keychain, P>>) {
+pub fn run_balance_cmd<A: BlockAnchor, P: ChainPosition>(
+    tracker: &Mutex<KeychainTracker<Keychain, A, P>>,
+) {
     let tracker = tracker.lock().unwrap();
     let (confirmed, unconfirmed) =
         tracker
@@ -258,9 +261,9 @@ pub fn run_balance_cmd<P: ChainPosition>(tracker: &Mutex<KeychainTracker<Keychai
     println!("unconfirmed: {}", unconfirmed);
 }
 
-pub fn run_txo_cmd<K: Debug + Clone + Ord, P: ChainPosition>(
+pub fn run_txo_cmd<K: Debug + Clone + Ord, A: BlockAnchor, P: ChainPosition>(
     txout_cmd: TxOutCmd,
-    tracker: &Mutex<KeychainTracker<K, P>>,
+    tracker: &Mutex<KeychainTracker<K, A, P>>,
     network: Network,
 ) {
     match txout_cmd {
@@ -313,11 +316,11 @@ pub fn run_txo_cmd<K: Debug + Clone + Ord, P: ChainPosition>(
 }
 
 #[allow(clippy::type_complexity)] // FIXME
-pub fn create_tx<P: ChainPosition>(
+pub fn create_tx<A: BlockAnchor, P: ChainPosition>(
     value: u64,
     address: Address,
     coin_select: CoinSelectionAlgo,
-    keychain_tracker: &mut KeychainTracker<Keychain, P>,
+    keychain_tracker: &mut KeychainTracker<Keychain, A, P>,
     keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
 ) -> Result<(
     Transaction,
@@ -526,19 +529,20 @@ pub fn create_tx<P: ChainPosition>(
     Ok((transaction, change_info))
 }
 
-pub fn handle_commands<C: clap::Subcommand, P>(
+pub fn handle_commands<C: clap::Subcommand, A, P>(
     command: Commands<C>,
     broadcast: impl FnOnce(&Transaction) -> Result<()>,
     // we Mutex around these not because we need them for a simple CLI app but to demonstrate how
     // all the stuff we're doing can be made thread-safe and not keep locks up over an IO bound.
-    tracker: &Mutex<KeychainTracker<Keychain, P>>,
-    store: &Mutex<KeychainStore<Keychain, P>>,
+    tracker: &Mutex<KeychainTracker<Keychain, A, P>>,
+    store: &Mutex<KeychainStore<Keychain, A, P>>,
     network: Network,
     keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
 ) -> Result<()>
 where
+    A: BlockAnchor,
     P: ChainPosition,
-    KeychainChangeSet<Keychain, P>: serde::Serialize + serde::de::DeserializeOwned,
+    KeychainChangeSet<Keychain, A, P>: serde::Serialize + serde::de::DeserializeOwned,
 {
     match command {
         // TODO: Make these functions return stuffs
@@ -619,17 +623,18 @@ where
 }
 
 #[allow(clippy::type_complexity)] // FIXME
-pub fn init<C: clap::Subcommand, P>() -> anyhow::Result<(
+pub fn init<C: clap::Subcommand, A, P>() -> anyhow::Result<(
     Args<C>,
     KeyMap,
     // These don't need to have mutexes around them, but we want the cli example code to make it obvious how they
     // are thread-safe, forcing the example developers to show where they would lock and unlock things.
-    Mutex<KeychainTracker<Keychain, P>>,
-    Mutex<KeychainStore<Keychain, P>>,
+    Mutex<KeychainTracker<Keychain, A, P>>,
+    Mutex<KeychainStore<Keychain, A, P>>,
 )>
 where
+    A: BlockAnchor,
     P: sparse_chain::ChainPosition,
-    KeychainChangeSet<Keychain, P>: serde::Serialize + serde::de::DeserializeOwned,
+    KeychainChangeSet<Keychain, A, P>: serde::Serialize + serde::de::DeserializeOwned,
 {
     let args = Args::<C>::parse();
     let secp = Secp256k1::default();
@@ -655,7 +660,7 @@ where
             .add_keychain(Keychain::Internal, internal_descriptor);
     };
 
-    let mut db = KeychainStore::<Keychain, P>::new_from_path(args.db_path.as_path())?;
+    let mut db = KeychainStore::<Keychain, A, P>::new_from_path(args.db_path.as_path())?;
 
     if let Err(e) = db.load_into_keychain_tracker(&mut tracker) {
         match tracker.chain().latest_checkpoint()  {
@@ -669,8 +674,8 @@ where
     Ok((args, keymap, Mutex::new(tracker), Mutex::new(db)))
 }
 
-pub fn planned_utxos<'a, AK: bdk_tmp_plan::CanDerive + Clone, P: ChainPosition>(
-    tracker: &'a KeychainTracker<Keychain, P>,
+pub fn planned_utxos<'a, AK: bdk_tmp_plan::CanDerive + Clone, A: BlockAnchor, P: ChainPosition>(
+    tracker: &'a KeychainTracker<Keychain, A, P>,
     assets: &'a bdk_tmp_plan::Assets<AK>,
 ) -> impl Iterator<Item = (bdk_tmp_plan::Plan<AK>, FullTxOut<P>)> + 'a {
     tracker