From f3a62c2f1c65e48cc1e5505a5e28702ba27f98ee Mon Sep 17 00:00:00 2001 From: SpaghettiSats Date: Sun, 10 Sep 2023 19:01:21 +0200 Subject: [PATCH 1/5] refactor: make chain_tip an Option and remove Default implementation for BlockId --- crates/bdk/src/wallet/mod.rs | 21 +++-- crates/chain/src/chain_data.rs | 15 +--- crates/chain/src/chain_oracle.rs | 4 +- crates/chain/src/local_chain.rs | 9 +- crates/chain/src/tx_graph.rs | 98 +++++++++++++-------- crates/chain/tests/test_indexed_tx_graph.rs | 6 +- crates/chain/tests/test_tx_graph.rs | 32 +++++-- example-crates/example_cli/src/lib.rs | 6 +- example-crates/example_electrum/src/main.rs | 2 +- example-crates/example_esplora/src/main.rs | 2 +- 10 files changed, 116 insertions(+), 79 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 9ee72b4b6..f5a5b5c33 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -437,7 +437,7 @@ impl Wallet { .graph() .filter_chain_unspents( &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), + self.chain.tip().map(|cp| cp.block_id()), self.indexed_graph.index.outpoints().iter().cloned(), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) @@ -487,7 +487,7 @@ impl Wallet { .graph() .filter_chain_unspents( &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), + self.chain.tip().map(|cp| cp.block_id()), core::iter::once((spk_i, op)), ) .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) @@ -660,7 +660,7 @@ impl Wallet { Some(CanonicalTx { chain_position: graph.get_chain_position( &self.chain, - self.chain.tip().map(|cp| cp.block_id()).unwrap_or_default(), + self.chain.tip().map(|cp| cp.block_id()), txid, )?, tx_node: graph.get_tx_node(txid)?, @@ -748,10 +748,9 @@ 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, self.chain.tip().map(|cp| cp.block_id())) } /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature @@ -759,7 +758,7 @@ 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.chain.tip().map(|cp| cp.block_id()), self.indexed_graph.index.outpoints().iter().cloned(), |&(k, _), _| k == KeychainKind::Internal, ) @@ -1239,7 +1238,7 @@ 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 chain_tip = self.chain.tip().map(|cp| cp.block_id()); let mut tx = graph .get_tx(txid) @@ -1474,7 +1473,7 @@ 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 chain_tip = self.chain.tip().map(|cp| cp.block_id()); let tx = &psbt.unsigned_tx; let mut finished = true; @@ -1643,7 +1642,7 @@ 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(); + let chain_tip = self.chain.tip().map(|cp| cp.block_id()); // must_spend <- manually selected utxos // may_spend <- all other available utxos let mut may_spend = self.get_available_utxos(); 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/chain_oracle.rs b/crates/chain/src/chain_oracle.rs index e736be035..27f04fe4c 100644 --- a/crates/chain/src/chain_oracle.rs +++ b/crates/chain/src/chain_oracle.rs @@ -14,10 +14,10 @@ pub trait ChainOracle { /// /// If `None` is returned, it means the implementation cannot determine whether `block` exists /// under `chain_tip`. - fn is_block_in_chain( + fn is_block_in_chain>( &self, block: BlockId, - chain_tip: BlockId, + chain_tip: Option, ) -> Result, Self::Error>; /// Get the best chain's chain tip. diff --git a/crates/chain/src/local_chain.rs b/crates/chain/src/local_chain.rs index d6cb20aa2..dcddc5402 100644 --- a/crates/chain/src/local_chain.rs +++ b/crates/chain/src/local_chain.rs @@ -171,11 +171,16 @@ impl From> for LocalChain { impl ChainOracle for LocalChain { type Error = Infallible; - fn is_block_in_chain( + fn is_block_in_chain>( &self, block: BlockId, - chain_tip: BlockId, + chain_tip: Option, ) -> Result, Self::Error> { + let chain_tip: BlockId = match chain_tip { + Some(x) => x.into(), + None => return Ok(None), + }; + if block.height > chain_tip.height { return Ok(None); } diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index cfd2de9d9..cca36315e 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -653,10 +653,10 @@ impl TxGraph { /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead. /// /// [`get_chain_position`]: Self::get_chain_position - pub fn try_get_chain_position( + pub fn try_get_chain_position + Clone, C: ChainOracle>( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, txid: Txid, ) -> Result>, C::Error> { let (tx_node, anchors, last_seen) = match self.txs.get(&txid) { @@ -665,7 +665,7 @@ impl TxGraph { }; for anchor in anchors { - match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? { + match chain.is_block_in_chain(anchor.anchor_block(), chain_tip.clone())? { Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))), _ => continue, } @@ -685,7 +685,7 @@ impl TxGraph { // this tx cannot exist in the best chain for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) { for block in conflicting_tx.anchors.iter().map(A::anchor_block) { - if chain.is_block_in_chain(block, chain_tip)? == Some(true) { + if chain.is_block_in_chain(block, chain_tip.clone())? == Some(true) { // conflicting tx is in best chain, so the current tx cannot be in best chain! return Ok(None); } @@ -703,10 +703,10 @@ impl TxGraph { /// This is the infallible version of [`try_get_chain_position`]. /// /// [`try_get_chain_position`]: Self::try_get_chain_position - pub fn get_chain_position>( + pub fn get_chain_position + Clone, C: ChainOracle>( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, txid: Txid, ) -> Option> { self.try_get_chain_position(chain, chain_tip, txid) @@ -725,21 +725,23 @@ impl TxGraph { /// 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( + pub fn try_get_chain_spend + Clone, C: ChainOracle>( &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_chain_position(chain, chain_tip.clone(), 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_chain_position(chain, chain_tip.clone(), txid)? + { return Ok(Some((observed_at, txid))); } } @@ -753,10 +755,10 @@ impl TxGraph { /// This is the infallible version of [`try_get_chain_spend`] /// /// [`try_get_chain_spend`]: Self::try_get_chain_spend - pub fn get_chain_spend>( + pub fn get_chain_spend + Clone, C: ChainOracle>( &self, chain: &C, - static_block: BlockId, + static_block: Option, outpoint: OutPoint, ) -> Option<(ChainPosition<&A>, Txid)> { self.try_get_chain_spend(chain, static_block, outpoint) @@ -776,13 +778,13 @@ impl TxGraph { /// 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>( + pub fn try_list_chain_txs<'a, B: Into + Clone, 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_chain_position(chain, chain_tip.clone(), tx.txid) .map(|v| { v.map(|observed_in| CanonicalTx { chain_position: observed_in, @@ -798,10 +800,10 @@ impl TxGraph { /// 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>( + pub fn list_chain_txs<'a, B: Into + Clone, C: ChainOracle + 'a>( &'a self, chain: &'a C, - chain_tip: BlockId, + chain_tip: Option, ) -> impl Iterator> { self.try_list_chain_txs(chain, chain_tip) .map(|r| r.expect("oracle is infallible")) @@ -825,10 +827,15 @@ impl TxGraph { /// instead. /// /// [`filter_chain_txouts`]: Self::filter_chain_txouts - pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + pub fn try_filter_chain_txouts< + 'a, + B: Into + Clone + '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 +853,13 @@ impl TxGraph { }; let chain_position = - match self.try_get_chain_position(chain, chain_tip, op.txid)? { + match self.try_get_chain_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, op)? + .try_get_chain_spend(chain, chain_tip.clone(), op)? .map(|(a, txid)| (a.cloned(), txid)); Ok(Some(( @@ -876,10 +883,15 @@ impl TxGraph { /// 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>( + pub fn filter_chain_txouts< + 'a, + B: Into + Clone + 'a, + C: ChainOracle + 'a, + OI: Clone + 'a, + >( &'a self, chain: &'a C, - chain_tip: BlockId, + chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { self.try_filter_chain_txouts(chain, chain_tip, outpoints) @@ -904,10 +916,15 @@ impl TxGraph { /// instead. /// /// [`filter_chain_unspents`]: Self::filter_chain_unspents - pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( + pub fn try_filter_chain_unspents< + 'a, + B: Into + Clone + '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) @@ -925,10 +942,15 @@ impl TxGraph { /// 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>( + pub fn filter_chain_unspents< + 'a, + B: Into + Clone + 'a, + C: ChainOracle + 'a, + OI: Clone + 'a, + >( &'a self, chain: &'a C, - chain_tip: BlockId, + chain_tip: Option, txouts: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { self.try_filter_chain_unspents(chain, chain_tip, txouts) @@ -947,10 +969,10 @@ impl TxGraph { /// used instead. /// /// [`balance`]: Self::balance - pub fn try_balance( + pub fn try_balance + Clone, C: ChainOracle, OI: Clone>( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, outpoints: impl IntoIterator, mut trust_predicate: impl FnMut(&OI, &Script) -> bool, ) -> Result { @@ -959,15 +981,19 @@ impl TxGraph { let mut untrusted_pending = 0; let mut confirmed = 0; - for res in self.try_filter_chain_unspents(chain, chain_tip, outpoints) { + 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) { 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_block_id { + 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(_) => { @@ -993,10 +1019,10 @@ impl TxGraph { /// This is the infallible version of [`try_balance`]. /// /// [`try_balance`]: Self::try_balance - pub fn balance, OI: Clone>( + pub fn balance + Clone, C: ChainOracle, OI: Clone>( &self, chain: &C, - chain_tip: BlockId, + chain_tip: Option, outpoints: impl IntoIterator, trust_predicate: impl FnMut(&OI, &Script) -> bool, ) -> Balance { diff --git a/crates/chain/tests/test_indexed_tx_graph.rs b/crates/chain/tests/test_indexed_tx_graph.rs index 84506ec11..2de8d938f 100644 --- a/crates/chain/tests/test_indexed_tx_graph.rs +++ b/crates/chain/tests/test_indexed_tx_graph.rs @@ -245,7 +245,7 @@ fn test_list_owned_txouts() { .graph() .filter_chain_txouts( &local_chain, - chain_tip, + Some(chain_tip), graph.index.outpoints().iter().cloned(), ) .collect::>(); @@ -254,14 +254,14 @@ fn test_list_owned_txouts() { .graph() .filter_chain_unspents( &local_chain, - chain_tip, + Some(chain_tip), graph.index.outpoints().iter().cloned(), ) .collect::>(); let balance = graph.graph().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..401f919d1 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -724,7 +724,11 @@ 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, + Some(tip.block_id()), + OutPoint::new(tx_0.txid(), 0) + ), Some(( ChainPosition::Confirmed(&ConfirmationHeightAnchor { anchor_block: tip.block_id(), @@ -736,7 +740,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, 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(), @@ -746,7 +750,11 @@ 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, + Some(tip.block_id()), + OutPoint::new(tx_0.txid(), 1) + ), Some((ChainPosition::Unconfirmed(0), tx_2.txid())), ); @@ -756,7 +764,11 @@ 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, + Some(tip.block_id()), + OutPoint::new(tx_0.txid(), 1) + ) .unwrap(), (ChainPosition::Unconfirmed(1234567), tx_2.txid()) ); @@ -773,7 +785,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, Some(tip.block_id()), tx_1_conflict.txid()) .is_none()); // Another conflicting tx that conflicts with tx_2. @@ -792,7 +804,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, Some(tip.block_id()), tx_2_conflict.txid()) .expect("position expected"), ChainPosition::Unconfirmed(1234568) ); @@ -800,14 +812,18 @@ 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, + Some(tip.block_id()), + 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, Some(tip.block_id()), 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..2c6ead527 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -252,7 +252,7 @@ pub fn run_balance_cmd( let balance = graph.graph().try_balance( chain, - chain.get_chain_tip()?.unwrap_or_default(), + chain.get_chain_tip()?, graph.index.outpoints().iter().cloned(), |(k, _), _| k == &Keychain::Internal, )?; @@ -289,7 +289,7 @@ 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 chain_tip = chain.get_chain_tip()?; let outpoints = graph.index.outpoints().iter().cloned(); match cmd { @@ -621,7 +621,7 @@ pub fn planned_utxos, ) -> Result, FullTxOut)>, O::Error> { - let chain_tip = chain.get_chain_tip()?.unwrap_or_default(); + let chain_tip = chain.get_chain_tip()?; let outpoints = graph.index.outpoints().iter().cloned(); graph .graph() diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index a05e85c57..d4d3f5efe 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -166,7 +166,7 @@ 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(); + let chain_tip = chain.tip().map(|cp| cp.block_id()); if !(all_spks || unused_spks || utxos || unconfirmed) { unused_spks = true; diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index 5791fe61a..4be7bcaec 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -206,7 +206,7 @@ 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(); + let chain_tip = chain.tip().map(|cp| cp.block_id()); if *all_spks { let all_spks = graph From ac54094caa5819ae73899baa3792a08225cc7de7 Mon Sep 17 00:00:00 2001 From: SpaghettiSats Date: Mon, 11 Sep 2023 01:16:37 +0200 Subject: [PATCH 2/5] refactor: implement new `TxGraph` methods that use the best `chain_tip` of the passed `chain` instead of specifying one. refactor: remove unnecessary tests --- crates/bdk/src/wallet/mod.rs | 18 +- crates/chain/src/tx_graph.rs | 323 +++++++++++++++++--- crates/chain/tests/test_indexed_tx_graph.rs | 6 +- crates/chain/tests/test_tx_graph.rs | 32 +- example-crates/example_cli/src/lib.rs | 6 +- example-crates/example_electrum/src/main.rs | 4 +- example-crates/example_esplora/src/main.rs | 4 +- 7 files changed, 305 insertions(+), 88 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index f5a5b5c33..9241d862b 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 cca36315e..99e9ef8f5 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 2de8d938f..021aa98c7 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(), diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 401f919d1..4748a8b0f 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -724,11 +724,7 @@ fn test_chain_spends() { // Assert that confirmed spends are returned correctly. assert_eq!( - graph.get_chain_spend( - &local_chain, - Some(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(), @@ -740,7 +736,7 @@ fn test_chain_spends() { // Check if chain position is returned correctly. assert_eq!( - graph.get_chain_position(&local_chain, Some(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(), @@ -750,11 +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, - Some(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())), ); @@ -764,11 +756,7 @@ fn test_chain_spends() { // Check chain spend returned correctly. assert_eq!( graph - .get_chain_spend( - &local_chain, - Some(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()) ); @@ -785,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, Some(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. @@ -804,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, Some(tip.block_id()), tx_2_conflict.txid()) + .get_chain_position(&local_chain, tx_2_conflict.txid()) .expect("position expected"), ChainPosition::Unconfirmed(1234568) ); @@ -812,18 +800,14 @@ fn test_chain_spends() { // Chain_spend now catches the new transaction as the spend. assert_eq!( graph - .get_chain_spend( - &local_chain, - Some(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, 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 2c6ead527..44ddae6bb 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 d4d3f5efe..6437a01d5 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 4be7bcaec..c938c6d2c 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::>(); From 1f70465d13a73dba6fd498bbf79e8bf001484871 Mon Sep 17 00:00:00 2001 From: SpaghettiSats Date: Thu, 14 Sep 2023 11:37:32 +0200 Subject: [PATCH 3/5] refactor: set concrete type `BlockId` instead of generic `B: Into` for `chain_tip` --- crates/bdk/src/wallet/mod.rs | 34 ++---- crates/chain/src/chain_oracle.rs | 4 +- crates/chain/src/local_chain.rs | 8 +- crates/chain/src/tx_graph.rs | 125 ++++++++++---------- example-crates/example_cli/src/lib.rs | 9 +- example-crates/example_electrum/src/main.rs | 5 +- example-crates/example_esplora/src/main.rs | 5 +- 7 files changed, 86 insertions(+), 104 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 9241d862b..b3e2854f4 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -435,9 +435,8 @@ impl Wallet { pub fn list_unspent(&self) -> impl Iterator + '_ { self.indexed_graph .graph() - .filter_subchain_unspents( + .filter_chain_unspents( &self.chain, - self.chain.tip().map(|cp| cp.block_id()), 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_subchain_unspents( - &self.chain, - self.chain.tip().map(|cp| cp.block_id()), - 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_subchain_position( - &self.chain, - self.chain.tip().map(|cp| cp.block_id()), - txid, - )?, + chain_position: graph.get_chain_position(&self.chain, txid)?, tx_node: graph.get_tx_node(txid)?, }) } @@ -748,17 +739,14 @@ impl Wallet { pub fn transactions( &self, ) -> impl Iterator> + '_ { - self.indexed_graph - .graph() - .list_subchain_txs(&self.chain, self.chain.tip().map(|cp| cp.block_id())) + self.indexed_graph.graph().list_chain_txs(&self.chain) } /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature /// values. pub fn get_balance(&self) -> Balance { - self.indexed_graph.graph().subchain_balance( + self.indexed_graph.graph().balance( &self.chain, - self.chain.tip().map(|cp| cp.block_id()), self.indexed_graph.index.outpoints().iter().cloned(), |&(k, _), _| k == KeychainKind::Internal, ) @@ -1238,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()); let mut tx = graph .get_tx(txid) @@ -1246,7 +1233,7 @@ impl Wallet { .clone(); let pos = graph - .get_subchain_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); @@ -1278,7 +1265,7 @@ impl Wallet { let txout = &prev_tx.output[txin.previous_output.vout as usize]; let confirmation_time: ConfirmationTime = graph - .get_subchain_position(&self.chain, chain_tip, txin.previous_output.txid) + .get_chain_position(&self.chain, txin.previous_output.txid) .ok_or(Error::UnknownUtxo)? .cloned() .into(); @@ -1473,8 +1460,6 @@ impl Wallet { psbt: &mut psbt::PartiallySignedTransaction, sign_options: SignOptions, ) -> Result { - let chain_tip = self.chain.tip().map(|cp| cp.block_id()); - let tx = &psbt.unsigned_tx; let mut finished = true; @@ -1489,7 +1474,7 @@ impl Wallet { let confirmation_height = self .indexed_graph .graph() - .get_subchain_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, @@ -1642,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()); // must_spend <- manually selected utxos // may_spend <- all other available utxos let mut may_spend = self.get_available_utxos(); @@ -1671,7 +1655,7 @@ impl Wallet { let confirmation_time: ConfirmationTime = match self .indexed_graph .graph() - .get_subchain_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_oracle.rs b/crates/chain/src/chain_oracle.rs index 27f04fe4c..2515adf8e 100644 --- a/crates/chain/src/chain_oracle.rs +++ b/crates/chain/src/chain_oracle.rs @@ -14,10 +14,10 @@ pub trait ChainOracle { /// /// If `None` is returned, it means the implementation cannot determine whether `block` exists /// under `chain_tip`. - fn is_block_in_chain>( + fn is_block_in_chain( &self, block: BlockId, - chain_tip: Option, + chain_tip: Option<&BlockId>, ) -> Result, Self::Error>; /// Get the best chain's chain tip. diff --git a/crates/chain/src/local_chain.rs b/crates/chain/src/local_chain.rs index dcddc5402..b3022d1cc 100644 --- a/crates/chain/src/local_chain.rs +++ b/crates/chain/src/local_chain.rs @@ -171,13 +171,13 @@ impl From> for LocalChain { impl ChainOracle for LocalChain { type Error = Infallible; - fn is_block_in_chain>( + fn is_block_in_chain( &self, block: BlockId, - chain_tip: Option, + chain_tip: Option<&BlockId>, ) -> Result, Self::Error> { - let chain_tip: BlockId = match chain_tip { - Some(x) => x.into(), + let chain_tip = match chain_tip { + Some(x) => x, None => return Ok(None), }; diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 99e9ef8f5..734ed2888 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -644,6 +644,11 @@ 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 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. /// @@ -652,11 +657,12 @@ impl TxGraph { /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the /// [`ChainOracle`] is infallible, [`get_subchain_position`] can be used instead. /// + /// [`try_get_chain_position`]: Self::try_get_chain_position /// [`get_subchain_position`]: Self::get_subchain_position - pub fn try_get_subchain_position + Clone, C: ChainOracle>( + pub fn try_get_subchain_position( &self, chain: &C, - chain_tip: Option, + chain_tip: Option, txid: Txid, ) -> Result>, C::Error> { let (tx_node, anchors, last_seen) = match self.txs.get(&txid) { @@ -665,7 +671,7 @@ impl TxGraph { }; for anchor in anchors { - match chain.is_block_in_chain(anchor.anchor_block(), chain_tip.clone())? { + match chain.is_block_in_chain(anchor.anchor_block(), chain_tip.as_ref())? { Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))), _ => continue, } @@ -685,7 +691,7 @@ impl TxGraph { // this tx cannot exist in the best chain for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) { for block in conflicting_tx.anchors.iter().map(A::anchor_block) { - if chain.is_block_in_chain(block, chain_tip.clone())? == Some(true) { + if chain.is_block_in_chain(block, chain_tip.as_ref())? == Some(true) { // conflicting tx is in best chain, so the current tx cannot be in best chain! return Ok(None); } @@ -722,10 +728,10 @@ impl TxGraph { /// 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>( + pub fn get_subchain_position>( &self, chain: &C, - chain_tip: Option, + chain_tip: Option, txid: Txid, ) -> Option> { self.try_get_subchain_position(chain, chain_tip, txid) @@ -751,30 +757,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 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_subchain_spend`] can be used instead. /// + /// [`try_get_chain_spend`]: Self::try_get_chain_spend /// [`get_subchain_spend`]: Self::get_subchain_spend - pub fn try_get_subchain_spend + Clone, C: ChainOracle>( + pub fn try_get_subchain_spend( &self, chain: &C, - chain_tip: Option, + chain_tip: Option, outpoint: OutPoint, ) -> Result, Txid)>, C::Error> { if self - .try_get_subchain_position(chain, chain_tip.clone(), 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_subchain_position(chain, chain_tip.clone(), txid)? - { + if let Some(observed_at) = self.try_get_subchain_position(chain, chain_tip, txid)? { return Ok(Some((observed_at, txid))); } } @@ -808,10 +817,10 @@ impl TxGraph { /// 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>( + pub fn get_subchain_spend>( &self, chain: &C, - chain_tip: Option, + chain_tip: Option, outpoint: OutPoint, ) -> Option<(ChainPosition<&A>, Txid)> { self.try_get_subchain_spend(chain, chain_tip, outpoint) @@ -835,6 +844,10 @@ impl TxGraph { /// 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 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`]. /// @@ -845,14 +858,15 @@ impl TxGraph { /// /// If the [`ChainOracle`] is infallible, [`list_subchain_txs`] can be used instead. /// + /// [`try_list_chain_txs`]: Self::try_list_chain_txs /// [`list_subchain_txs`]: Self::list_subchain_txs - pub fn try_list_subchain_txs<'a, B: Into + Clone, C: ChainOracle + 'a>( + pub fn try_list_subchain_txs<'a, C: ChainOracle + 'a>( &'a self, chain: &'a C, - chain_tip: Option, + chain_tip: Option, ) -> impl Iterator, C::Error>> { self.full_txs().filter_map(move |tx| { - self.try_get_subchain_position(chain, chain_tip.clone(), tx.txid) + self.try_get_subchain_position(chain, chain_tip, tx.txid) .map(|v| { v.map(|observed_in| CanonicalTx { chain_position: observed_in, @@ -892,10 +906,10 @@ impl TxGraph { /// 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>( + pub fn list_subchain_txs<'a, C: ChainOracle + 'a>( &'a self, chain: &'a C, - chain_tip: Option, + chain_tip: Option, ) -> impl Iterator> { self.try_list_subchain_txs(chain, chain_tip) .map(|r| r.expect("oracle is infallible")) @@ -917,6 +931,10 @@ impl TxGraph { /// 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 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. @@ -931,16 +949,12 @@ impl TxGraph { /// If the [`ChainOracle`] implementation is infallible, [`filter_subchain_txouts`] can be used /// instead. /// + /// [`try_filter_chain_txouts`]: Self::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, - OI: Clone + 'a, - >( + pub fn try_filter_subchain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( &'a self, chain: &'a C, - chain_tip: Option, + chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator), C::Error>> + 'a { outpoints @@ -958,13 +972,13 @@ impl TxGraph { }; let chain_position = - match self.try_get_subchain_position(chain, chain_tip.clone(), 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_subchain_spend(chain, chain_tip.clone(), op)? + .try_get_subchain_spend(chain, chain_tip, op)? .map(|(a, txid)| (a.cloned(), txid)); Ok(Some(( @@ -1017,15 +1031,10 @@ impl TxGraph { /// 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, - B: Into + Clone + 'a, - C: ChainOracle + 'a, - OI: Clone + 'a, - >( + pub fn filter_subchain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>( &'a self, chain: &'a C, - chain_tip: Option, + chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { self.try_filter_subchain_txouts(chain, chain_tip, outpoints) @@ -1049,6 +1058,10 @@ impl TxGraph { /// 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 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. @@ -1063,16 +1076,12 @@ impl TxGraph { /// If the [`ChainOracle`] implementation is infallible, [`filter_subchain_unspents`] can be used /// instead. /// + /// [`try_filter_chain_unspents`]: Self::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, - OI: Clone + 'a, - >( + pub fn try_filter_subchain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( &'a self, chain: &'a C, - chain_tip: Option, + chain_tip: Option, outpoints: impl IntoIterator + 'a, ) -> impl Iterator), C::Error>> + 'a { self.try_filter_subchain_txouts(chain, chain_tip, outpoints) @@ -1120,15 +1129,10 @@ impl TxGraph { /// 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, - B: Into + Clone + 'a, - C: ChainOracle + 'a, - OI: Clone + 'a, - >( + pub fn filter_subchain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>( &'a self, chain: &'a C, - chain_tip: Option, + chain_tip: Option, txouts: impl IntoIterator + 'a, ) -> impl Iterator)> + 'a { self.try_filter_subchain_unspents(chain, chain_tip, txouts) @@ -1152,6 +1156,10 @@ impl TxGraph { /// 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 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 @@ -1161,11 +1169,12 @@ impl TxGraph { /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`subchain_balance`] can be /// used instead. /// + /// [`try_balance`]: Self::try_balance /// [`subchain_balance`]: Self::subchain_balance - pub fn try_subchain_balance + Clone, C: ChainOracle, OI: Clone>( + pub fn try_subchain_balance( &self, chain: &C, - chain_tip: Option, + chain_tip: Option, outpoints: impl IntoIterator, mut trust_predicate: impl FnMut(&OI, &Script) -> bool, ) -> Result { @@ -1174,14 +1183,12 @@ impl TxGraph { let mut untrusted_pending = 0; let mut confirmed = 0; - let chain_tip_block_id = chain_tip.map(|block| block.into()); - - for res in self.try_filter_subchain_unspents(chain, chain_tip_block_id, 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 let Some(block_id) = chain_tip_block_id { + 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) { @@ -1233,14 +1240,10 @@ impl TxGraph { /// 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, - >( + pub fn subchain_balance, OI: Clone>( &self, chain: &C, - chain_tip: Option, + chain_tip: Option, outpoints: impl IntoIterator, trust_predicate: impl FnMut(&OI, &Script) -> bool, ) -> Balance { diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 44ddae6bb..f97ba4f45 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -250,9 +250,8 @@ pub fn run_balance_cmd( } } - let balance = graph.graph().try_subchain_balance( + let balance = graph.graph().try_balance( chain, - chain.get_chain_tip()?, 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()?; let outpoints = graph.index.outpoints().iter().cloned(); match cmd { @@ -301,7 +299,7 @@ where } => { let txouts = graph .graph() - .try_filter_subchain_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()?; let outpoints = graph.index.outpoints().iter().cloned(); graph .graph() - .try_filter_subchain_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 6437a01d5..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()); if !(all_spks || unused_spks || utxos || unconfirmed) { unused_spks = true; @@ -214,7 +213,7 @@ fn main() -> anyhow::Result<()> { let utxos = graph .graph() - .filter_subchain_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_subchain_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 c938c6d2c..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()); 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_subchain_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_subchain_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::>(); From 41ca63e29d7819c4e78f62a8295e0072af435f76 Mon Sep 17 00:00:00 2001 From: SpaghettiSats Date: Thu, 21 Sep 2023 12:35:29 +0200 Subject: [PATCH 4/5] docs: improve chain tip definition --- crates/chain/src/tx_graph.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 734ed2888..eb2114029 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -645,7 +645,7 @@ 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 tip. + /// 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. /// @@ -758,7 +758,7 @@ 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 tip. You can use this to find information + /// 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 @@ -845,7 +845,7 @@ impl TxGraph { /// 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 tip. You can use this to find information + /// 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 @@ -932,7 +932,7 @@ impl TxGraph { /// `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 tip. You can use this to find information + /// 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 @@ -1059,7 +1059,7 @@ impl TxGraph { /// `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 tip. You can use this to find information + /// 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 @@ -1157,7 +1157,7 @@ impl TxGraph { /// 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 tip. You can use this to find information + /// 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. From a93ef52410b5518ac67f52311b96942ee73f24c4 Mon Sep 17 00:00:00 2001 From: SpaghettiSats Date: Mon, 2 Oct 2023 22:23:41 +0200 Subject: [PATCH 5/5] refactor: remove a meaningless `Option` --- crates/chain/src/chain_oracle.rs | 2 +- crates/chain/src/local_chain.rs | 7 +------ crates/chain/src/tx_graph.rs | 9 +++++++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/chain/src/chain_oracle.rs b/crates/chain/src/chain_oracle.rs index 2515adf8e..e736be035 100644 --- a/crates/chain/src/chain_oracle.rs +++ b/crates/chain/src/chain_oracle.rs @@ -17,7 +17,7 @@ pub trait ChainOracle { fn is_block_in_chain( &self, block: BlockId, - chain_tip: Option<&BlockId>, + chain_tip: BlockId, ) -> Result, Self::Error>; /// Get the best chain's chain tip. diff --git a/crates/chain/src/local_chain.rs b/crates/chain/src/local_chain.rs index b3022d1cc..d6cb20aa2 100644 --- a/crates/chain/src/local_chain.rs +++ b/crates/chain/src/local_chain.rs @@ -174,13 +174,8 @@ impl ChainOracle for LocalChain { fn is_block_in_chain( &self, block: BlockId, - chain_tip: Option<&BlockId>, + chain_tip: BlockId, ) -> Result, Self::Error> { - let chain_tip = match chain_tip { - Some(x) => x, - None => return Ok(None), - }; - if block.height > chain_tip.height { return Ok(None); } diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index eb2114029..e03bc884d 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -665,13 +665,18 @@ impl TxGraph { 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), }; for anchor in anchors { - match chain.is_block_in_chain(anchor.anchor_block(), chain_tip.as_ref())? { + match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? { Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))), _ => continue, } @@ -691,7 +696,7 @@ impl TxGraph { // this tx cannot exist in the best chain for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) { for block in conflicting_tx.anchors.iter().map(A::anchor_block) { - if chain.is_block_in_chain(block, chain_tip.as_ref())? == Some(true) { + if chain.is_block_in_chain(block, chain_tip)? == Some(true) { // conflicting tx is in best chain, so the current tx cannot be in best chain! return Ok(None); }