diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 9ee72b4b6..b3e2854f4 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -437,7 +437,6 @@ impl Wallet { .graph() .filter_chain_unspents( &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), self.indexed_graph.index.outpoints().iter().cloned(), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) @@ -485,11 +484,7 @@ impl Wallet { let (&spk_i, _) = self.indexed_graph.index.txout(op)?; self.indexed_graph .graph() - .filter_chain_unspents( - &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), - core::iter::once((spk_i, op)), - ) + .filter_chain_unspents(&self.chain, core::iter::once((spk_i, op))) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) .next() } @@ -658,11 +653,7 @@ impl Wallet { let graph = self.indexed_graph.graph(); Some(CanonicalTx { - chain_position: graph.get_chain_position( - &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), - txid, - )?, + chain_position: graph.get_chain_position(&self.chain, txid)?, tx_node: graph.get_tx_node(txid)?, }) } @@ -748,10 +739,7 @@ impl Wallet { pub fn transactions( &self, ) -> impl Iterator> + '_ { - self.indexed_graph.graph().list_chain_txs( - &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), - ) + self.indexed_graph.graph().list_chain_txs(&self.chain) } /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature @@ -759,7 +747,6 @@ impl Wallet { pub fn get_balance(&self) -> Balance { self.indexed_graph.graph().balance( &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), self.indexed_graph.index.outpoints().iter().cloned(), |&(k, _), _| k == KeychainKind::Internal, ) @@ -1239,7 +1226,6 @@ impl Wallet { ) -> Result, Error> { let graph = self.indexed_graph.graph(); let txout_index = &self.indexed_graph.index; - let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(); let mut tx = graph .get_tx(txid) @@ -1247,7 +1233,7 @@ impl Wallet { .clone(); let pos = graph - .get_chain_position(&self.chain, chain_tip, txid) + .get_chain_position(&self.chain, txid) .ok_or(Error::TransactionNotFound)?; if let ChainPosition::Confirmed(_) = pos { return Err(Error::TransactionConfirmed); @@ -1279,7 +1265,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_chain_position(&self.chain, txin.previous_output.txid) .ok_or(Error::UnknownUtxo)? .cloned() .into(); @@ -1474,8 +1460,6 @@ impl Wallet { psbt: &mut psbt::PartiallySignedTransaction, sign_options: SignOptions, ) -> Result { - let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(); - let tx = &psbt.unsigned_tx; let mut finished = true; @@ -1490,7 +1474,7 @@ impl Wallet { let confirmation_height = self .indexed_graph .graph() - .get_chain_position(&self.chain, chain_tip, input.previous_output.txid) + .get_chain_position(&self.chain, input.previous_output.txid) .map(|chain_position| match chain_position { ChainPosition::Confirmed(a) => a.confirmation_height, ChainPosition::Unconfirmed(_) => u32::MAX, @@ -1643,7 +1627,6 @@ impl Wallet { must_only_use_confirmed_tx: bool, current_height: Option, ) -> (Vec, Vec) { - let chain_tip = self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(); // must_spend <- manually selected utxos // may_spend <- all other available utxos let mut may_spend = self.get_available_utxos(); @@ -1672,7 +1655,7 @@ impl Wallet { let confirmation_time: ConfirmationTime = match self .indexed_graph .graph() - .get_chain_position(&self.chain, chain_tip, txid) + .get_chain_position(&self.chain, txid) { Some(chain_position) => chain_position.cloned().into(), None => return false, diff --git a/crates/chain/src/chain_data.rs b/crates/chain/src/chain_data.rs index 550854298..7209720f0 100644 --- a/crates/chain/src/chain_data.rs +++ b/crates/chain/src/chain_data.rs @@ -1,4 +1,4 @@ -use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid}; +use bitcoin::{BlockHash, OutPoint, TxOut, Txid}; use crate::{Anchor, COINBASE_MATURITY}; @@ -109,15 +109,6 @@ impl Anchor for BlockId { } } -impl Default for BlockId { - fn default() -> Self { - Self { - height: Default::default(), - hash: BlockHash::all_zeros(), - } - } -} - impl From<(u32, BlockHash)> for BlockId { fn from((height, hash): (u32, BlockHash)) -> Self { Self { height, hash } @@ -142,7 +133,7 @@ impl From<(&u32, &BlockHash)> for BlockId { /// An [`Anchor`] implementation that also records the exact confirmation height of the transaction. /// /// Refer to [`Anchor`] for more details. -#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), @@ -172,7 +163,7 @@ impl Anchor for ConfirmationHeightAnchor { /// transaction. /// /// Refer to [`Anchor`] for more details. -#[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] #[cfg_attr( feature = "serde", derive(serde::Deserialize, serde::Serialize), diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index cfd2de9d9..e03bc884d 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -644,21 +644,32 @@ impl TxGraph { /// Get the position of the transaction in `chain` with tip `chain_tip`. /// + /// This method is like [`try_get_chain_position`] except it restricts the + /// chain to a custom tip. The tip doesn't even need to be in the same chain as the current tip. + /// You can use this to find information about a point in the past or on a fork if your + /// chain oracle supports that. + /// /// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `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. + /// [`ChainOracle`] is infallible, [`get_subchain_position`] can be used instead. /// - /// [`get_chain_position`]: Self::get_chain_position - pub fn try_get_chain_position( + /// [`try_get_chain_position`]: Self::try_get_chain_position + /// [`get_subchain_position`]: Self::get_subchain_position + pub fn try_get_subchain_position( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, txid: Txid, ) -> Result>, C::Error> { + let chain_tip = match chain_tip { + Some(tip) => tip, + None => return Ok(None), + }; + let (tx_node, anchors, last_seen) = match self.txs.get(&txid) { Some(v) => v, None => return Ok(None), @@ -698,18 +709,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>( + &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>( &self, chain: &C, - chain_tip: BlockId, txid: Txid, ) -> Option> { - self.try_get_chain_position(chain, chain_tip, txid) + self.try_get_chain_position(chain, txid) .expect("error is infallible") } @@ -718,28 +762,33 @@ impl TxGraph { /// /// If no in-chain transaction spends `outpoint`, `None` will be returned. /// + /// This method is like [`try_get_chain_spend`] except it restricts the chain to a custom tip. + /// The tip doesn't even need to be in the same chain as the current tip. You can use this to find information + /// about a point in the past or on a fork if your chain oracle supports that. + /// /// # Error /// /// 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( + /// [`try_get_chain_spend`]: Self::try_get_chain_spend + /// [`get_subchain_spend`]: Self::get_subchain_spend + pub fn try_get_subchain_spend( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, outpoint: OutPoint, ) -> Result, Txid)>, C::Error> { if self - .try_get_chain_position(chain, chain_tip, outpoint.txid)? + .try_get_subchain_position(chain, chain_tip, outpoint.txid)? .is_none() { return Ok(None); } 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, txid)? { + if let Some(observed_at) = self.try_get_subchain_position(chain, chain_tip, txid)? { return Ok(Some((observed_at, txid))); } } @@ -747,24 +796,63 @@ 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>( + &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>( &self, chain: &C, - static_block: BlockId, 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") } /// List graph transactions that are in `chain` with `chain_tip`. /// + /// This method is like `try_list_chain_txs` except it restricts the chain to a custom tip. + /// The tip doesn't even need to be in the same chain as the current tip. You can use this to find information + /// about a point in the past or on a fork if your chain oracle supports that. + /// /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is /// observed in-chain, and the [`TxNode`]. /// @@ -773,16 +861,17 @@ 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, C: ChainOracle + 'a>( + /// [`try_list_chain_txs`]: Self::try_list_chain_txs + /// [`list_subchain_txs`]: Self::list_subchain_txs + pub fn try_list_subchain_txs<'a, C: ChainOracle + 'a>( &'a self, chain: &'a C, - chain_tip: BlockId, + chain_tip: Option, ) -> impl Iterator, C::Error>> { self.full_txs().filter_map(move |tx| { - self.try_get_chain_position(chain, chain_tip, tx.txid) + self.try_get_subchain_position(chain, chain_tip, tx.txid) .map(|v| { v.map(|observed_in| CanonicalTx { chain_position: observed_in, @@ -793,23 +882,64 @@ 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, 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, C: ChainOracle + 'a>( &'a self, chain: &'a C, - chain_tip: BlockId, ) -> impl Iterator> { - self.try_list_chain_txs(chain, chain_tip) + self.try_list_chain_txs(chain) .map(|r| r.expect("oracle is infallible")) } /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with /// `chain_tip`. /// + /// This method is like [`try_filter_chain_txouts`] except it restricts the chain to a custom tip. + /// The tip doesn't even need to be in the same chain as the current tip. You can use this to find information + /// about a point in the past or on a fork if your chain oracle supports that. + /// /// `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. @@ -821,14 +951,15 @@ 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<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts + /// [`filter_subchain_txouts`]: Self::filter_subchain_txouts + pub fn try_filter_subchain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( &'a self, chain: &'a C, - chain_tip: BlockId, + chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator), C::Error>> + 'a { outpoints @@ -846,13 +977,13 @@ impl TxGraph { }; let chain_position = - match self.try_get_chain_position(chain, chain_tip, op.txid)? { + match self.try_get_subchain_position(chain, chain_tip, op.txid)? { Some(pos) => pos.cloned(), None => return Ok(None), }; let spent_by = self - .try_get_chain_spend(chain, chain_tip, op)? + .try_get_subchain_spend(chain, chain_tip, op)? .map(|(a, txid)| (a.cloned(), txid)); Ok(Some(( @@ -870,25 +1001,72 @@ 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_subchain_txouts`]. + /// + /// [`try_filter_subchain_txouts`]: Self::try_filter_subchain_txouts + pub fn filter_subchain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + &'a self, + chain: &'a C, + chain_tip: Option, + outpoints: impl IntoIterator + 'a, + ) -> impl Iterator)> + 'a { + 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, - chain_tip: BlockId, outpoints: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { - self.try_filter_chain_txouts(chain, chain_tip, outpoints) + self.try_filter_chain_txouts(chain, outpoints) .map(|r| r.expect("oracle is infallible")) } /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in /// `chain` with `chain_tip`. /// + /// This method is like [`try_filter_chain_unspents`] except it restricts the chain to a custom tip. + /// The tip doesn't even need to be in the same chain as the current tip. You can use this to find information + /// about a point in the past or on a fork if your chain oracle supports that. + /// /// `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. @@ -900,17 +1078,18 @@ 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<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents + /// [`filter_subchain_unspents`]: Self::filter_subchain_unspents + pub fn try_filter_subchain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( &'a self, chain: &'a C, - chain_tip: BlockId, + 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(), @@ -919,38 +1098,88 @@ 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_subchain_unspents`]. + /// + /// [`try_filter_subchain_unspents`]: Self::try_filter_subchain_unspents + pub fn filter_subchain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + &'a self, + chain: &'a C, + chain_tip: Option, + txouts: impl IntoIterator + 'a, + ) -> impl Iterator)> + 'a { + 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, - chain_tip: BlockId, txouts: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { - self.try_filter_chain_unspents(chain, chain_tip, txouts) + self.try_filter_chain_unspents(chain, txouts) .map(|r| r.expect("oracle is infallible")) } /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`. /// + /// This method is like [`try_balance`] except it restricts the chain to a custom tip. + /// The tip doesn't even need to be in the same chain as the current tip. You can use this to find information + /// about a point in the past or on a fork if your chain oracle supports that. + /// /// 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 + /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`subchain_balance`] can be /// used instead. /// - /// [`balance`]: Self::balance - pub fn try_balance( + /// [`try_balance`]: Self::try_balance + /// [`subchain_balance`]: Self::subchain_balance + pub fn try_subchain_balance( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, outpoints: impl IntoIterator, mut trust_predicate: impl FnMut(&OI, &Script) -> bool, ) -> Result { @@ -959,15 +1188,17 @@ impl TxGraph { let mut untrusted_pending = 0; let mut confirmed = 0; - for res in self.try_filter_chain_unspents(chain, chain_tip, outpoints) { + for res in self.try_filter_subchain_unspents(chain, chain_tip, outpoints) { let (spk_i, txout) = res?; match &txout.chain_position { ChainPosition::Confirmed(_) => { - if txout.is_confirmed_and_spendable(chain_tip.height) { - confirmed += txout.txout.value; - } else if !txout.is_mature(chain_tip.height) { - immature += txout.txout.value; + if let Some(block_id) = chain_tip { + if txout.is_confirmed_and_spendable(block_id.height) { + confirmed += txout.txout.value; + } else if !txout.is_mature(block_id.height) { + immature += txout.txout.value; + } } } ChainPosition::Unconfirmed(_) => { @@ -988,19 +1219,55 @@ 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, 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, OI: Clone>( &self, chain: &C, - chain_tip: BlockId, 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 84506ec11..021aa98c7 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -243,25 +243,25 @@ 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, - chain_tip, + Some(chain_tip), graph.index.outpoints().iter().cloned(), ) .collect::>(); let utxos = graph .graph() - .filter_chain_unspents( + .filter_subchain_unspents( &local_chain, - chain_tip, + Some(chain_tip), graph.index.outpoints().iter().cloned(), ) .collect::>(); - let balance = graph.graph().balance( + let balance = graph.graph().subchain_balance( &local_chain, - chain_tip, + Some(chain_tip), graph.index.outpoints().iter().cloned(), |_, spk: &Script| trusted_spks.contains(&spk.to_owned()), ); diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 4c68f5108..4748a8b0f 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(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 0)), + graph.get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 0)), Some(( ChainPosition::Confirmed(&ConfirmationHeightAnchor { anchor_block: tip.block_id(), @@ -736,7 +736,7 @@ fn test_chain_spends() { // Check if chain position is returned correctly. assert_eq!( - graph.get_chain_position(&local_chain, tip.block_id(), tx_0.txid()), + 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(), @@ -746,7 +746,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(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 1)), + graph.get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 1)), Some((ChainPosition::Unconfirmed(0), tx_2.txid())), ); @@ -756,7 +756,7 @@ fn test_chain_spends() { // Check chain spend returned correctly. assert_eq!( graph - .get_chain_spend(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 1)) + .get_chain_spend(&local_chain, OutPoint::new(tx_0.txid(), 1)) .unwrap(), (ChainPosition::Unconfirmed(1234567), tx_2.txid()) ); @@ -773,7 +773,7 @@ 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, tip.block_id(), tx_1_conflict.txid()) + .get_chain_position(&local_chain, tx_1_conflict.txid()) .is_none()); // Another conflicting tx that conflicts with tx_2. @@ -792,7 +792,7 @@ fn test_chain_spends() { // This should return a valid observation with correct last seen. assert_eq!( graph - .get_chain_position(&local_chain, tip.block_id(), tx_2_conflict.txid()) + .get_chain_position(&local_chain, tx_2_conflict.txid()) .expect("position expected"), ChainPosition::Unconfirmed(1234568) ); @@ -800,14 +800,14 @@ fn test_chain_spends() { // Chain_spend now catches the new transaction as the spend. assert_eq!( graph - .get_chain_spend(&local_chain, tip.block_id(), OutPoint::new(tx_0.txid(), 1)) + .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_chain_position(&local_chain, 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 c9459c353..f97ba4f45 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -252,7 +252,6 @@ pub fn run_balance_cmd( let balance = graph.graph().try_balance( chain, - chain.get_chain_tip()?.unwrap_or_default(), graph.index.outpoints().iter().cloned(), |(k, _), _| k == &Keychain::Internal, )?; @@ -289,7 +288,6 @@ pub fn run_txo_cmd( where O::Error: std::error::Error + Send + Sync + 'static, { - let chain_tip = chain.get_chain_tip()?.unwrap_or_default(); let outpoints = graph.index.outpoints().iter().cloned(); match cmd { @@ -301,7 +299,7 @@ where } => { let txouts = graph .graph() - .try_filter_chain_txouts(chain, chain_tip, outpoints) + .try_filter_chain_txouts(chain, outpoints) .filter(|r| match r { Ok((_, full_txo)) => match (spent, unspent) { (true, false) => full_txo.spent_by.is_some(), @@ -621,11 +619,10 @@ pub fn planned_utxos, ) -> Result, FullTxOut)>, O::Error> { - let chain_tip = chain.get_chain_tip()?.unwrap_or_default(); let outpoints = graph.index.outpoints().iter().cloned(); graph .graph() - .try_filter_chain_unspents(chain, chain_tip, outpoints) + .try_filter_chain_unspents(chain, outpoints) .filter_map( #[allow(clippy::type_complexity)] |r| -> Option, FullTxOut), _>> { diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index a05e85c57..db33c52fb 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -166,7 +166,6 @@ fn main() -> anyhow::Result<()> { // Get a short lock on the tracker to get the spks we're interested in let graph = graph.lock().unwrap(); let chain = chain.lock().unwrap(); - let chain_tip = chain.tip().map(|cp| cp.block_id()).unwrap_or_default(); if !(all_spks || unused_spks || utxos || unconfirmed) { unused_spks = true; @@ -214,7 +213,7 @@ fn main() -> anyhow::Result<()> { let utxos = graph .graph() - .filter_chain_unspents(&*chain, chain_tip, init_outpoints) + .filter_chain_unspents(&*chain, init_outpoints) .map(|(_, utxo)| utxo) .collect::>(); @@ -236,7 +235,7 @@ fn main() -> anyhow::Result<()> { if unconfirmed { let unconfirmed_txids = graph .graph() - .list_chain_txs(&*chain, chain_tip) + .list_chain_txs(&*chain) .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 5791fe61a..bcf481dd8 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -206,7 +206,6 @@ fn main() -> anyhow::Result<()> { { let graph = graph.lock().unwrap(); let chain = chain.lock().unwrap(); - let chain_tip = chain.tip().map(|cp| cp.block_id()).unwrap_or_default(); if *all_spks { let all_spks = graph @@ -246,7 +245,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_chain_unspents(&*chain, init_outpoints) .map(|(_, utxo)| utxo) .collect::>(); outpoints = Box::new( @@ -269,7 +268,7 @@ fn main() -> anyhow::Result<()> { // `EsploraExt::update_tx_graph_without_keychain`. let unconfirmed_txids = graph .graph() - .list_chain_txs(&*chain, chain_tip) + .list_chain_txs(&*chain) .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) .map(|canonical_tx| canonical_tx.tx_node.txid) .collect::>();