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