diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index f5a5b5c33e..9241d862b7 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -435,7 +435,7 @@ impl Wallet { pub fn list_unspent(&self) -> impl Iterator + '_ { self.indexed_graph .graph() - .filter_chain_unspents( + .filter_subchain_unspents( &self.chain, self.chain.tip().map(|cp| cp.block_id()), self.indexed_graph.index.outpoints().iter().cloned(), @@ -485,7 +485,7 @@ impl Wallet { let (&spk_i, _) = self.indexed_graph.index.txout(op)?; self.indexed_graph .graph() - .filter_chain_unspents( + .filter_subchain_unspents( &self.chain, self.chain.tip().map(|cp| cp.block_id()), core::iter::once((spk_i, op)), @@ -658,7 +658,7 @@ impl Wallet { let graph = self.indexed_graph.graph(); Some(CanonicalTx { - chain_position: graph.get_chain_position( + chain_position: graph.get_subchain_position( &self.chain, self.chain.tip().map(|cp| cp.block_id()), txid, @@ -750,13 +750,13 @@ impl Wallet { ) -> impl Iterator> + '_ { self.indexed_graph .graph() - .list_chain_txs(&self.chain, self.chain.tip().map(|cp| cp.block_id())) + .list_subchain_txs(&self.chain, self.chain.tip().map(|cp| cp.block_id())) } /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature /// values. pub fn get_balance(&self) -> Balance { - self.indexed_graph.graph().balance( + self.indexed_graph.graph().subchain_balance( &self.chain, self.chain.tip().map(|cp| cp.block_id()), self.indexed_graph.index.outpoints().iter().cloned(), @@ -1246,7 +1246,7 @@ impl Wallet { .clone(); let pos = graph - .get_chain_position(&self.chain, chain_tip, txid) + .get_subchain_position(&self.chain, chain_tip, txid) .ok_or(Error::TransactionNotFound)?; if let ChainPosition::Confirmed(_) = pos { return Err(Error::TransactionConfirmed); @@ -1278,7 +1278,7 @@ impl Wallet { let txout = &prev_tx.output[txin.previous_output.vout as usize]; let confirmation_time: ConfirmationTime = graph - .get_chain_position(&self.chain, chain_tip, txin.previous_output.txid) + .get_subchain_position(&self.chain, chain_tip, txin.previous_output.txid) .ok_or(Error::UnknownUtxo)? .cloned() .into(); @@ -1489,7 +1489,7 @@ impl Wallet { let confirmation_height = self .indexed_graph .graph() - .get_chain_position(&self.chain, chain_tip, input.previous_output.txid) + .get_subchain_position(&self.chain, chain_tip, input.previous_output.txid) .map(|chain_position| match chain_position { ChainPosition::Confirmed(a) => a.confirmation_height, ChainPosition::Unconfirmed(_) => u32::MAX, @@ -1671,7 +1671,7 @@ impl Wallet { let confirmation_time: ConfirmationTime = match self .indexed_graph .graph() - .get_chain_position(&self.chain, chain_tip, txid) + .get_subchain_position(&self.chain, chain_tip, txid) { Some(chain_position) => chain_position.cloned().into(), None => return false, diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index cca36315e4..99e9ef8f54 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -650,10 +650,10 @@ impl TxGraph { /// # Error /// /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the - /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead. + /// [`ChainOracle`] is infallible, [`get_subchain_position`] can be used instead. /// - /// [`get_chain_position`]: Self::get_chain_position - pub fn try_get_chain_position + Clone, C: ChainOracle>( + /// [`get_subchain_position`]: Self::get_subchain_position + pub fn try_get_subchain_position + Clone, C: ChainOracle>( &self, chain: &C, chain_tip: Option, @@ -698,18 +698,51 @@ impl TxGraph { Ok(Some(ChainPosition::Unconfirmed(*last_seen))) } + /// Get the position of the transaction in `chain`. + /// + /// If the given transaction of `txid` does not exist in the chain, `None` is + /// returned. + /// + /// # Error + /// + /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the + /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead. + /// + /// [`get_chain_position`]: Self::get_chain_position + pub fn try_get_chain_position( + &self, + chain: &C, + txid: Txid, + ) -> Result>, C::Error> { + self.try_get_subchain_position(chain, chain.get_chain_tip()?, txid) + } + /// Get the position of the transaction in `chain` with tip `chain_tip`. /// + /// This is the infallible version of [`try_get_subchain_position`]. + /// + /// [`try_get_subchain_position`]: Self::try_get_subchain_position + pub fn get_subchain_position + Clone, C: ChainOracle>( + &self, + chain: &C, + chain_tip: Option, + txid: Txid, + ) -> Option> { + self.try_get_subchain_position(chain, chain_tip, txid) + .expect("error is infallible") + } + + /// Get the position of the transaction in `chain`. + /// /// This is the infallible version of [`try_get_chain_position`]. /// /// [`try_get_chain_position`]: Self::try_get_chain_position - pub fn get_chain_position + Clone, C: ChainOracle>( + pub fn get_chain_position>( &self, chain: &C, - chain_tip: Option, txid: Txid, ) -> Option> { - self.try_get_chain_position(chain, chain_tip, txid) + self.try_get_chain_position(chain, txid) .expect("error is infallible") } @@ -722,17 +755,17 @@ impl TxGraph { /// /// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails. /// - /// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead. + /// If the [`ChainOracle`] is infallible, [`get_subchain_spend`] can be used instead. /// - /// [`get_chain_spend`]: Self::get_chain_spend - pub fn try_get_chain_spend + Clone, C: ChainOracle>( + /// [`get_subchain_spend`]: Self::get_subchain_spend + pub fn try_get_subchain_spend + Clone, C: ChainOracle>( &self, chain: &C, chain_tip: Option, outpoint: OutPoint, ) -> Result, Txid)>, C::Error> { if self - .try_get_chain_position(chain, chain_tip.clone(), outpoint.txid)? + .try_get_subchain_position(chain, chain_tip.clone(), outpoint.txid)? .is_none() { return Ok(None); @@ -740,7 +773,7 @@ impl TxGraph { if let Some(spends) = self.spends.get(&outpoint) { for &txid in spends { if let Some(observed_at) = - self.try_get_chain_position(chain, chain_tip.clone(), txid)? + self.try_get_subchain_position(chain, chain_tip.clone(), txid)? { return Ok(Some((observed_at, txid))); } @@ -749,19 +782,54 @@ impl TxGraph { Ok(None) } + /// Get the txid of the spending transaction and where the spending transaction is observed in + /// the `chain`. + /// + /// If no in-chain transaction spends `outpoint`, `None` will be returned. + /// + /// # Error + /// + /// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails. + /// + /// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead. + /// + /// [`get_chain_spend`]: Self::get_chain_spend + pub fn try_get_chain_spend( + &self, + chain: &C, + outpoint: OutPoint, + ) -> Result, Txid)>, C::Error> { + self.try_get_subchain_spend(chain, chain.get_chain_tip()?, outpoint) + } + /// Get the txid of the spending transaction and where the spending transaction is observed in /// the `chain` of `chain_tip`. /// + /// This is the infallible version of [`try_get_subchain_spend`] + /// + /// [`try_get_subchain_spend`]: Self::try_get_subchain_spend + pub fn get_subchain_spend + Clone, C: ChainOracle>( + &self, + chain: &C, + chain_tip: Option, + outpoint: OutPoint, + ) -> Option<(ChainPosition<&A>, Txid)> { + self.try_get_subchain_spend(chain, chain_tip, outpoint) + .expect("error is infallible") + } + + /// Get the txid of the spending transaction and where the spending transaction is observed in + /// the `chain`. + /// /// This is the infallible version of [`try_get_chain_spend`] /// /// [`try_get_chain_spend`]: Self::try_get_chain_spend - pub fn get_chain_spend + Clone, C: ChainOracle>( + pub fn get_chain_spend>( &self, chain: &C, - static_block: Option, outpoint: OutPoint, ) -> Option<(ChainPosition<&A>, Txid)> { - self.try_get_chain_spend(chain, static_block, outpoint) + self.try_get_chain_spend(chain, outpoint) .expect("error is infallible") } @@ -775,16 +843,16 @@ impl TxGraph { /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the /// returned item. /// - /// If the [`ChainOracle`] is infallible, [`list_chain_txs`] can be used instead. + /// If the [`ChainOracle`] is infallible, [`list_subchain_txs`] can be used instead. /// - /// [`list_chain_txs`]: Self::list_chain_txs - pub fn try_list_chain_txs<'a, B: Into + Clone, C: ChainOracle + 'a>( + /// [`list_subchain_txs`]: Self::list_subchain_txs + pub fn try_list_subchain_txs<'a, B: Into + Clone, C: ChainOracle + 'a>( &'a self, chain: &'a C, chain_tip: Option, ) -> impl Iterator, C::Error>> { self.full_txs().filter_map(move |tx| { - self.try_get_chain_position(chain, chain_tip.clone(), tx.txid) + self.try_get_subchain_position(chain, chain_tip.clone(), tx.txid) .map(|v| { v.map(|observed_in| CanonicalTx { chain_position: observed_in, @@ -795,17 +863,54 @@ impl TxGraph { }) } + /// List graph transactions that are in `chain`. + /// + /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is + /// observed in-chain, and the [`TxNode`]. + /// + /// # Error + /// + /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the + /// returned item. + /// + /// If the [`ChainOracle`] is infallible, [`list_chain_txs`] can be used instead. + /// + /// [`list_chain_txs`]: Self::list_chain_txs + pub fn try_list_chain_txs<'a, C: ChainOracle + 'a>( + &'a self, + chain: &'a C, + ) -> impl Iterator, C::Error>> { + let chain_tip = match chain.get_chain_tip() { + Ok(tip) => tip, + Err(_) => None, + }; + self.try_list_subchain_txs(chain, chain_tip) + } + /// List graph transactions that are in `chain` with `chain_tip`. /// + /// This is the infallible version of [`try_list_subchain_txs`]. + /// + /// [`try_list_subchain_txs`]: Self::try_list_subchain_txs + pub fn list_subchain_txs<'a, B: Into + Clone, C: ChainOracle + 'a>( + &'a self, + chain: &'a C, + chain_tip: Option, + ) -> impl Iterator> { + self.try_list_subchain_txs(chain, chain_tip) + .map(|r| r.expect("oracle is infallible")) + } + + /// List graph transactions that are in `chain`. + /// /// This is the infallible version of [`try_list_chain_txs`]. /// /// [`try_list_chain_txs`]: Self::try_list_chain_txs - pub fn list_chain_txs<'a, B: Into + Clone, C: ChainOracle + 'a>( + pub fn list_chain_txs<'a, C: ChainOracle + 'a>( &'a self, chain: &'a C, - chain_tip: Option, ) -> impl Iterator> { - self.try_list_chain_txs(chain, chain_tip) + self.try_list_chain_txs(chain) .map(|r| r.expect("oracle is infallible")) } @@ -823,11 +928,11 @@ impl TxGraph { /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`) /// fails. /// - /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used + /// If the [`ChainOracle`] implementation is infallible, [`filter_subchain_txouts`] can be used /// instead. /// - /// [`filter_chain_txouts`]: Self::filter_chain_txouts - pub fn try_filter_chain_txouts< + /// [`filter_subchain_txouts`]: Self::filter_subchain_txouts + pub fn try_filter_subchain_txouts< 'a, B: Into + Clone + 'a, C: ChainOracle + 'a, @@ -853,13 +958,13 @@ impl TxGraph { }; let chain_position = - match self.try_get_chain_position(chain, chain_tip.clone(), op.txid)? { + match self.try_get_subchain_position(chain, chain_tip.clone(), op.txid)? { Some(pos) => pos.cloned(), None => return Ok(None), }; let spent_by = self - .try_get_chain_spend(chain, chain_tip.clone(), op)? + .try_get_subchain_spend(chain, chain_tip.clone(), op)? .map(|(a, txid)| (a.cloned(), txid)); Ok(Some(( @@ -877,13 +982,42 @@ impl TxGraph { .filter_map(Result::transpose) } + /// Get a filtered list of outputs from the given `outpoints` that are in `chain`. + /// + /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier + /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or + /// [`Iterator::enumerate`] over a list of [`OutPoint`]s. + /// + /// Floating outputs are ignored. + /// + /// # Error + /// + /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`) + /// fails. + /// + /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used + /// instead. + /// + /// [`filter_chain_txouts`]: Self::filter_chain_txouts + pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + &'a self, + chain: &'a C, + outpoints: impl IntoIterator + 'a, + ) -> impl Iterator), C::Error>> + 'a { + let chain_tip = match chain.get_chain_tip() { + Ok(tip) => tip, + Err(_) => None, + }; + self.try_filter_subchain_txouts(chain, chain_tip, outpoints) + } + /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with /// `chain_tip`. /// - /// This is the infallible version of [`try_filter_chain_txouts`]. + /// This is the infallible version of [`try_filter_subchain_txouts`]. /// - /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts - pub fn filter_chain_txouts< + /// [`try_filter_subchain_txouts`]: Self::try_filter_subchain_txouts + pub fn filter_subchain_txouts< 'a, B: Into + Clone + 'a, C: ChainOracle + 'a, @@ -894,7 +1028,21 @@ impl TxGraph { chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { - self.try_filter_chain_txouts(chain, chain_tip, outpoints) + self.try_filter_subchain_txouts(chain, chain_tip, outpoints) + .map(|r| r.expect("oracle is infallible")) + } + + /// Get a filtered list of outputs from the given `outpoints` that are in `chain`. + /// + /// This is the infallible version of [`try_filter_chain_txouts`]. + /// + /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts + pub fn filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + &'a self, + chain: &'a C, + outpoints: impl IntoIterator + 'a, + ) -> impl Iterator)> + 'a { + self.try_filter_chain_txouts(chain, outpoints) .map(|r| r.expect("oracle is infallible")) } @@ -912,11 +1060,11 @@ impl TxGraph { /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`) /// fails. /// - /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used + /// If the [`ChainOracle`] implementation is infallible, [`filter_subchain_unspents`] can be used /// instead. /// - /// [`filter_chain_unspents`]: Self::filter_chain_unspents - pub fn try_filter_chain_unspents< + /// [`filter_subchain_unspents`]: Self::filter_subchain_unspents + pub fn try_filter_subchain_unspents< 'a, B: Into + Clone + 'a, C: ChainOracle + 'a, @@ -927,7 +1075,7 @@ impl TxGraph { chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator), C::Error>> + 'a { - self.try_filter_chain_txouts(chain, chain_tip, outpoints) + self.try_filter_subchain_txouts(chain, chain_tip, outpoints) .filter(|r| match r { // keep unspents, drop spents Ok((_, full_txo)) => full_txo.spent_by.is_none(), @@ -936,13 +1084,43 @@ impl TxGraph { }) } + /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in + /// `chain`. + /// + /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier + /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or + /// [`Iterator::enumerate`] over a list of [`OutPoint`]s. + /// + /// Floating outputs are ignored. + /// + /// # Error + /// + /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`) + /// fails. + /// + /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used + /// instead. + /// + /// [`filter_chain_unspents`]: Self::filter_chain_unspents + pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + &'a self, + chain: &'a C, + outpoints: impl IntoIterator + 'a, + ) -> impl Iterator), C::Error>> + 'a { + let chain_tip = match chain.get_chain_tip() { + Ok(tip) => tip, + Err(_) => None, + }; + self.try_filter_subchain_unspents(chain, chain_tip, outpoints) + } + /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in /// `chain` with `chain_tip`. /// - /// This is the infallible version of [`try_filter_chain_unspents`]. + /// This is the infallible version of [`try_filter_subchain_unspents`]. /// - /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents - pub fn filter_chain_unspents< + /// [`try_filter_subchain_unspents`]: Self::try_filter_subchain_unspents + pub fn filter_subchain_unspents< 'a, B: Into + Clone + 'a, C: ChainOracle + 'a, @@ -953,7 +1131,22 @@ impl TxGraph { chain_tip: Option, txouts: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { - self.try_filter_chain_unspents(chain, chain_tip, txouts) + self.try_filter_subchain_unspents(chain, chain_tip, txouts) + .map(|r| r.expect("oracle is infallible")) + } + + /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in + /// `chain`. + /// + /// This is the infallible version of [`try_filter_chain_unspents`]. + /// + /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents + pub fn filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + &'a self, + chain: &'a C, + txouts: impl IntoIterator + 'a, + ) -> impl Iterator)> + 'a { + self.try_filter_chain_unspents(chain, txouts) .map(|r| r.expect("oracle is infallible")) } @@ -965,11 +1158,11 @@ impl TxGraph { /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or /// [`Iterator::enumerate`] over a list of [`OutPoint`]s. /// - /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be + /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`subchain_balance`] can be /// used instead. /// - /// [`balance`]: Self::balance - pub fn try_balance + Clone, C: ChainOracle, OI: Clone>( + /// [`subchain_balance`]: Self::subchain_balance + pub fn try_subchain_balance + Clone, C: ChainOracle, OI: Clone>( &self, chain: &C, chain_tip: Option, @@ -983,7 +1176,7 @@ impl TxGraph { let chain_tip_block_id = chain_tip.map(|block| block.into()); - for res in self.try_filter_chain_unspents(chain, chain_tip_block_id, outpoints) { + for res in self.try_filter_subchain_unspents(chain, chain_tip_block_id, outpoints) { let (spk_i, txout) = res?; match &txout.chain_position { @@ -1014,19 +1207,59 @@ impl TxGraph { }) } + /// Get the total balance of `outpoints` that are in `chain`. + /// + /// The output of `trust_predicate` should return `true` for scripts that we trust. + /// + /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier + /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or + /// [`Iterator::enumerate`] over a list of [`OutPoint`]s. + /// + /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be + /// used instead. + /// + /// [`balance`]: Self::balance + pub fn try_balance( + &self, + chain: &C, + outpoints: impl IntoIterator, + trust_predicate: impl FnMut(&OI, &Script) -> bool, + ) -> Result { + self.try_subchain_balance(chain, chain.get_chain_tip()?, outpoints, trust_predicate) + } + /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`. /// + /// This is the infallible version of [`try_subchain_balance`]. + /// + /// [`try_subchain_balance`]: Self::try_subchain_balance + pub fn subchain_balance< + B: Into + Clone, + C: ChainOracle, + OI: Clone, + >( + &self, + chain: &C, + chain_tip: Option, + outpoints: impl IntoIterator, + trust_predicate: impl FnMut(&OI, &Script) -> bool, + ) -> Balance { + self.try_subchain_balance(chain, chain_tip, outpoints, trust_predicate) + .expect("oracle is infallible") + } + + /// Get the total balance of `outpoints` that are in `chain`. + /// /// This is the infallible version of [`try_balance`]. /// /// [`try_balance`]: Self::try_balance - pub fn balance + Clone, C: ChainOracle, OI: Clone>( + pub fn balance, OI: Clone>( &self, chain: &C, - chain_tip: Option, outpoints: impl IntoIterator, trust_predicate: impl FnMut(&OI, &Script) -> bool, ) -> Balance { - self.try_balance(chain, chain_tip, outpoints, trust_predicate) + self.try_balance(chain, outpoints, trust_predicate) .expect("oracle is infallible") } } diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 2de8d938fd..621c1c6966 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -243,7 +243,7 @@ fn test_list_owned_txouts() { .unwrap_or_else(|| panic!("block must exist at {}", height)); let txouts = graph .graph() - .filter_chain_txouts( + .filter_subchain_txouts( &local_chain, Some(chain_tip), graph.index.outpoints().iter().cloned(), @@ -252,14 +252,14 @@ fn test_list_owned_txouts() { let utxos = graph .graph() - .filter_chain_unspents( + .filter_subchain_unspents( &local_chain, Some(chain_tip), graph.index.outpoints().iter().cloned(), ) .collect::>(); - let balance = graph.graph().balance( + let balance = graph.graph().subchain_balance( &local_chain, Some(chain_tip), graph.index.outpoints().iter().cloned(), @@ -457,7 +457,13 @@ fn test_list_owned_txouts() { // AT Block 99 { - let (_, _, _, _, balance) = fetch(100, &graph); + let ( + confirmed_txouts_txid, + unconfirmed_txouts_txid, + confirmed_utxos_txid, + unconfirmed_utxos_txid, + balance, + ) = fetch(100, &graph); // Coinbase maturity hits assert_eq!( @@ -469,5 +475,80 @@ fn test_list_owned_txouts() { confirmed: 80000 // tx1 + tx3 } ); + + // Test that not specifing the chain_tip return the same results + let txouts = graph + .graph() + .filter_chain_txouts(&local_chain, graph.index.outpoints().iter().cloned()) + .collect::>(); + + let utxos = graph + .graph() + .filter_chain_unspents(&local_chain, graph.index.outpoints().iter().cloned()) + .collect::>(); + + let balance_ = graph.graph().balance( + &local_chain, + graph.index.outpoints().iter().cloned(), + |_, spk: &Script| trusted_spks.contains(&spk.to_owned()), + ); + + let confirmed_txouts_txid_ = txouts + .iter() + .filter_map(|(_, full_txout)| { + if matches!(full_txout.chain_position, ChainPosition::Confirmed(_)) { + Some(full_txout.outpoint.txid) + } else { + None + } + }) + .collect::>(); + + let unconfirmed_txouts_txid_ = txouts + .iter() + .filter_map(|(_, full_txout)| { + if matches!(full_txout.chain_position, ChainPosition::Unconfirmed(_)) { + Some(full_txout.outpoint.txid) + } else { + None + } + }) + .collect::>(); + + let confirmed_utxos_txid_ = utxos + .iter() + .filter_map(|(_, full_txout)| { + if matches!(full_txout.chain_position, ChainPosition::Confirmed(_)) { + Some(full_txout.outpoint.txid) + } else { + None + } + }) + .collect::>(); + + let unconfirmed_utxos_txid_ = utxos + .iter() + .filter_map(|(_, full_txout)| { + if matches!(full_txout.chain_position, ChainPosition::Unconfirmed(_)) { + Some(full_txout.outpoint.txid) + } else { + None + } + }) + .collect::>(); + + assert_eq!(confirmed_txouts_txid, confirmed_txouts_txid_); + assert_eq!(unconfirmed_txouts_txid, unconfirmed_txouts_txid_); + assert_eq!(confirmed_utxos_txid, confirmed_utxos_txid_); + assert_eq!(unconfirmed_utxos_txid, unconfirmed_utxos_txid_); + assert_eq!( + confirmed_txouts_txid.len() + unconfirmed_txouts_txid.len(), + txouts.len() + ); + assert_eq!( + confirmed_utxos_txid.len() + unconfirmed_utxos_txid.len(), + utxos.len() + ); + assert_eq!(balance, balance_); } } diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 401f919d1d..0bccc74414 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -724,7 +724,7 @@ fn test_chain_spends() { // Assert that confirmed spends are returned correctly. assert_eq!( - graph.get_chain_spend( + graph.get_subchain_spend( &local_chain, Some(tip.block_id()), OutPoint::new(tx_0.txid(), 0) @@ -738,9 +738,31 @@ fn test_chain_spends() { )), ); + // Assert that confirmed spends are returned correctly. + assert_eq!( + graph.get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 0)), + Some(( + ChainPosition::Confirmed(&ConfirmationHeightAnchor { + anchor_block: tip.block_id(), + confirmation_height: 98 + }), + tx_1.txid(), + )), + ); + // Check if chain position is returned correctly. assert_eq!( - graph.get_chain_position(&local_chain, Some(tip.block_id()), tx_0.txid()), + graph.get_subchain_position(&local_chain, Some(tip.block_id()), tx_0.txid()), + // Some(ObservedAs::Confirmed(&local_chain.get_block(95).expect("block expected"))), + Some(ChainPosition::Confirmed(&ConfirmationHeightAnchor { + anchor_block: tip.block_id(), + confirmation_height: 95 + })) + ); + + // Check if chain position is returned correctly. + assert_eq!( + graph.get_chain_position(&local_chain, tx_0.txid()), // Some(ObservedAs::Confirmed(&local_chain.get_block(95).expect("block expected"))), Some(ChainPosition::Confirmed(&ConfirmationHeightAnchor { anchor_block: tip.block_id(), @@ -750,7 +772,7 @@ fn test_chain_spends() { // Even if unconfirmed tx has a last_seen of 0, it can still be part of a chain spend. assert_eq!( - graph.get_chain_spend( + graph.get_subchain_spend( &local_chain, Some(tip.block_id()), OutPoint::new(tx_0.txid(), 1) @@ -758,13 +780,19 @@ fn test_chain_spends() { Some((ChainPosition::Unconfirmed(0), tx_2.txid())), ); + // Even if unconfirmed tx has a last_seen of 0, it can still be part of a chain spend. + assert_eq!( + graph.get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 1)), + Some((ChainPosition::Unconfirmed(0), tx_2.txid())), + ); + // Mark the unconfirmed as seen and check correct ObservedAs status is returned. let _ = graph.insert_seen_at(tx_2.txid(), 1234567); // Check chain spend returned correctly. assert_eq!( graph - .get_chain_spend( + .get_subchain_spend( &local_chain, Some(tip.block_id()), OutPoint::new(tx_0.txid(), 1) @@ -773,6 +801,14 @@ fn test_chain_spends() { (ChainPosition::Unconfirmed(1234567), tx_2.txid()) ); + // Check chain spend returned correctly. + assert_eq!( + graph + .get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 1)) + .unwrap(), + (ChainPosition::Unconfirmed(1234567), tx_2.txid()) + ); + // A conflicting transaction that conflicts with tx_1. let tx_1_conflict = Transaction { input: vec![TxIn { @@ -785,7 +821,12 @@ fn test_chain_spends() { // Because this tx conflicts with an already confirmed transaction, chain position should return none. assert!(graph - .get_chain_position(&local_chain, Some(tip.block_id()), tx_1_conflict.txid()) + .get_subchain_position(&local_chain, Some(tip.block_id()), tx_1_conflict.txid()) + .is_none()); + + // Because this tx conflicts with an already confirmed transaction, chain position should return none. + assert!(graph + .get_chain_position(&local_chain, tx_1_conflict.txid()) .is_none()); // Another conflicting tx that conflicts with tx_2. @@ -804,7 +845,15 @@ fn test_chain_spends() { // This should return a valid observation with correct last seen. assert_eq!( graph - .get_chain_position(&local_chain, Some(tip.block_id()), tx_2_conflict.txid()) + .get_subchain_position(&local_chain, Some(tip.block_id()), tx_2_conflict.txid()) + .expect("position expected"), + ChainPosition::Unconfirmed(1234568) + ); + + // This should return a valid observation with correct last seen. + assert_eq!( + graph + .get_chain_position(&local_chain, tx_2_conflict.txid()) .expect("position expected"), ChainPosition::Unconfirmed(1234568) ); @@ -812,7 +861,7 @@ fn test_chain_spends() { // Chain_spend now catches the new transaction as the spend. assert_eq!( graph - .get_chain_spend( + .get_subchain_spend( &local_chain, Some(tip.block_id()), OutPoint::new(tx_0.txid(), 1) @@ -821,9 +870,22 @@ fn test_chain_spends() { (ChainPosition::Unconfirmed(1234568), tx_2_conflict.txid()) ); + // Chain_spend now catches the new transaction as the spend. + assert_eq!( + graph + .get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 1)) + .expect("expect observation"), + (ChainPosition::Unconfirmed(1234568), tx_2_conflict.txid()) + ); + + // Chain position of the `tx_2` is now none, as it is older than `tx_2_conflict` + assert!(graph + .get_subchain_position(&local_chain, Some(tip.block_id()), tx_2.txid()) + .is_none()); + // Chain position of the `tx_2` is now none, as it is older than `tx_2_conflict` assert!(graph - .get_chain_position(&local_chain, Some(tip.block_id()), tx_2.txid()) + .get_chain_position(&local_chain, tx_2.txid()) .is_none()); } diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 2c6ead5279..44ddae6bba 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -250,7 +250,7 @@ pub fn run_balance_cmd( } } - let balance = graph.graph().try_balance( + let balance = graph.graph().try_subchain_balance( chain, chain.get_chain_tip()?, graph.index.outpoints().iter().cloned(), @@ -301,7 +301,7 @@ where } => { let txouts = graph .graph() - .try_filter_chain_txouts(chain, chain_tip, outpoints) + .try_filter_subchain_txouts(chain, chain_tip, outpoints) .filter(|r| match r { Ok((_, full_txo)) => match (spent, unspent) { (true, false) => full_txo.spent_by.is_some(), @@ -625,7 +625,7 @@ pub fn planned_utxos Option, FullTxOut), _>> { diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index d4d3f5efe7..6437a01d53 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -214,7 +214,7 @@ fn main() -> anyhow::Result<()> { let utxos = graph .graph() - .filter_chain_unspents(&*chain, chain_tip, init_outpoints) + .filter_subchain_unspents(&*chain, chain_tip, init_outpoints) .map(|(_, utxo)| utxo) .collect::>(); @@ -236,7 +236,7 @@ fn main() -> anyhow::Result<()> { if unconfirmed { let unconfirmed_txids = graph .graph() - .list_chain_txs(&*chain, chain_tip) + .list_subchain_txs(&*chain, chain_tip) .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) .map(|canonical_tx| canonical_tx.tx_node.txid) .collect::>(); diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index 4be7bcaec8..c938c6d2cd 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -246,7 +246,7 @@ fn main() -> anyhow::Result<()> { let init_outpoints = graph.index.outpoints().iter().cloned(); let utxos = graph .graph() - .filter_chain_unspents(&*chain, chain_tip, init_outpoints) + .filter_subchain_unspents(&*chain, chain_tip, init_outpoints) .map(|(_, utxo)| utxo) .collect::>(); outpoints = Box::new( @@ -269,7 +269,7 @@ fn main() -> anyhow::Result<()> { // `EsploraExt::update_tx_graph_without_keychain`. let unconfirmed_txids = graph .graph() - .list_chain_txs(&*chain, chain_tip) + .list_subchain_txs(&*chain, chain_tip) .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) .map(|canonical_tx| canonical_tx.tx_node.txid) .collect::>();