println!("\ntook: {}s", start.elapsed().as_secs());
println!("Local tip: {}", chain.tip().height());
let unspent: Vec<_> = graph
- .graph()
- .filter_chain_unspents(
- &chain,
- chain.tip().block_id(),
- Default::default(),
- graph.index.outpoints().clone(),
- )
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .filter_unspent_outpoints(graph.index.outpoints().clone())
.collect();
if !unspent.is_empty() {
println!("\nUnspent");
}
}
- for canon_tx in graph.graph().list_canonical_txs(
- &chain,
- chain.tip().block_id(),
- bdk_chain::CanonicalizationParams::default(),
- ) {
- if !canon_tx.chain_position.is_confirmed() {
- eprintln!(
- "ERROR: canonical tx should be confirmed {}",
- canon_tx.tx_node.txid
- );
+ for canon_tx in graph
+ .canonical_view(
+ &chain,
+ chain.tip().block_id(),
+ bdk_chain::CanonicalizationParams::default(),
+ )
+ .txs()
+ {
+ if !canon_tx.pos.is_confirmed() {
+ eprintln!("ERROR: canonical tx should be confirmed {}", canon_tx.txid);
}
}
) -> anyhow::Result<Balance> {
let chain_tip = recv_chain.tip().block_id();
let outpoints = recv_graph.index.outpoints().clone();
- let balance = recv_graph.graph().balance(
- recv_chain,
- chain_tip,
- CanonicalizationParams::default(),
- outpoints,
- |_, _| true,
- );
+ let balance = recv_graph
+ .canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
+ .balance(outpoints, |_, _| true);
Ok(balance)
}
// Retrieve the expected unconfirmed txids and spks from the graph.
let exp_spk_txids = graph
- .list_expected_spk_txids(&chain, chain_tip, ..)
+ .canonical_view(&chain, chain_tip, Default::default())
+ .list_expected_spk_txids(&graph.index, ..)
.collect::<Vec<_>>();
assert_eq!(exp_spk_txids, vec![(spk, txid_1)]);
let _ = graph.batch_insert_relevant_evicted_at(mempool_event.evicted);
let canonical_txids = graph
- .graph()
- .list_canonical_txs(&chain, chain_tip, CanonicalizationParams::default())
- .map(|tx| tx.tx_node.compute_txid())
+ .canonical_view(&chain, chain_tip, CanonicalizationParams::default())
+ .txs()
+ .map(|tx| tx.txid)
.collect::<Vec<_>>();
// tx1 should no longer be canonical.
assert!(!canonical_txids.contains(&txid_1));
}
fn run_list_canonical_txs(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txs: usize) {
- let txs = tx_graph.graph().list_canonical_txs(
+ let view = tx_graph.canonical_view(
chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
);
+ let txs = view.txs();
assert_eq!(txs.count(), exp_txs);
}
fn run_filter_chain_txouts(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_txos: usize) {
- let utxos = tx_graph.graph().filter_chain_txouts(
+ let view = tx_graph.canonical_view(
chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
- tx_graph.index.outpoints().clone(),
);
+ let utxos = view.filter_outpoints(tx_graph.index.outpoints().clone());
assert_eq!(utxos.count(), exp_txos);
}
fn run_filter_chain_unspents(tx_graph: &KeychainTxGraph, chain: &LocalChain, exp_utxos: usize) {
- let utxos = tx_graph.graph().filter_chain_unspents(
+ let view = tx_graph.canonical_view(
chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
- tx_graph.index.outpoints().clone(),
);
+ let utxos = view.filter_unspent_outpoints(tx_graph.index.outpoints().clone());
assert_eq!(utxos.count(), exp_utxos);
}
// Check balance
let chain_tip = chain.tip().block_id();
let op = graph.index.outpoints().clone();
- let bal = graph.graph().balance(
- chain,
- chain_tip,
- CanonicalizationParams::default(),
- op,
- |_, _| false,
- );
+ let bal = graph
+ .canonical_view(chain, chain_tip, CanonicalizationParams::default())
+ .balance(op, |_, _| false);
assert_eq!(bal.total(), AMOUNT * TX_CT as u64);
}
--- /dev/null
+//! Canonical view.
+
+use crate::collections::HashMap;
+use alloc::sync::Arc;
+use core::{fmt, ops::RangeBounds};
+
+use alloc::vec::Vec;
+
+use bdk_core::BlockId;
+use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, Txid};
+
+use crate::{
+ spk_txout::SpkTxOutIndex, tx_graph::TxNode, Anchor, Balance, CanonicalIter, CanonicalReason,
+ CanonicalizationParams, ChainOracle, ChainPosition, FullTxOut, ObservedIn, TxGraph,
+};
+
+/// A single canonical transaction.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CanonicalViewTx<A> {
+ /// Chain position.
+ pub pos: ChainPosition<A>,
+ /// Transaction ID.
+ pub txid: Txid,
+ /// The actual transaction.
+ pub tx: Arc<Transaction>,
+}
+
+/// A view of canonical transactions.
+#[derive(Debug)]
+pub struct CanonicalView<A> {
+ order: Vec<Txid>,
+ txs: HashMap<Txid, (Arc<Transaction>, ChainPosition<A>)>,
+ spends: HashMap<OutPoint, Txid>,
+ tip: BlockId,
+}
+
+impl<A: Anchor> CanonicalView<A> {
+ /// Create a canonical view.
+ pub fn new<'g, C>(
+ tx_graph: &'g TxGraph<A>,
+ chain: &'g C,
+ chain_tip: BlockId,
+ params: CanonicalizationParams,
+ ) -> Result<Self, C::Error>
+ where
+ C: ChainOracle,
+ {
+ fn find_direct_anchor<'g, A: Anchor, C: ChainOracle>(
+ tx_node: &TxNode<'g, Arc<Transaction>, A>,
+ chain: &C,
+ chain_tip: BlockId,
+ ) -> Result<Option<A>, C::Error> {
+ tx_node
+ .anchors
+ .iter()
+ .find_map(|a| -> Option<Result<A, C::Error>> {
+ match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
+ Ok(Some(true)) => Some(Ok(a.clone())),
+ Ok(Some(false)) | Ok(None) => None,
+ Err(err) => Some(Err(err)),
+ }
+ })
+ .transpose()
+ }
+
+ let mut view = Self {
+ tip: chain_tip,
+ order: vec![],
+ txs: HashMap::new(),
+ spends: HashMap::new(),
+ };
+
+ for r in CanonicalIter::new(tx_graph, chain, chain_tip, params) {
+ let (txid, tx, why) = r?;
+
+ let tx_node = match tx_graph.get_tx_node(txid) {
+ Some(tx_node) => tx_node,
+ None => {
+ // TODO: Have the `CanonicalIter` return `TxNode`s.
+ debug_assert!(false, "tx node must exist!");
+ continue;
+ }
+ };
+
+ view.order.push(txid);
+
+ if !tx.is_coinbase() {
+ view.spends
+ .extend(tx.input.iter().map(|txin| (txin.previous_output, txid)));
+ }
+
+ let pos = match why {
+ CanonicalReason::Assumed { descendant } => match descendant {
+ Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
+ Some(anchor) => ChainPosition::Confirmed {
+ anchor,
+ transitively: None,
+ },
+ None => ChainPosition::Unconfirmed {
+ first_seen: tx_node.first_seen,
+ last_seen: tx_node.last_seen,
+ },
+ },
+ None => ChainPosition::Unconfirmed {
+ first_seen: tx_node.first_seen,
+ last_seen: tx_node.last_seen,
+ },
+ },
+ CanonicalReason::Anchor { anchor, descendant } => match descendant {
+ Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
+ Some(anchor) => ChainPosition::Confirmed {
+ anchor,
+ transitively: None,
+ },
+ None => ChainPosition::Confirmed {
+ anchor,
+ transitively: descendant,
+ },
+ },
+ None => ChainPosition::Confirmed {
+ anchor,
+ transitively: None,
+ },
+ },
+ CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
+ ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
+ first_seen: tx_node.first_seen,
+ last_seen: Some(last_seen),
+ },
+ ObservedIn::Block(_) => ChainPosition::Unconfirmed {
+ first_seen: tx_node.first_seen,
+ last_seen: None,
+ },
+ },
+ };
+ view.txs.insert(txid, (tx_node.tx, pos));
+ }
+
+ Ok(view)
+ }
+
+ /// Get a single canonical transaction.
+ pub fn tx(&self, txid: Txid) -> Option<CanonicalViewTx<A>> {
+ self.txs
+ .get(&txid)
+ .cloned()
+ .map(|(tx, pos)| CanonicalViewTx { pos, txid, tx })
+ }
+
+ /// Get a single canonical txout.
+ pub fn txout(&self, op: OutPoint) -> Option<FullTxOut<A>> {
+ let (tx, pos) = self.txs.get(&op.txid)?;
+ let vout: usize = op.vout.try_into().ok()?;
+ let txout = tx.output.get(vout)?;
+ let spent_by = self.spends.get(&op).map(|spent_by_txid| {
+ let (_, spent_by_pos) = &self.txs[spent_by_txid];
+ (spent_by_pos.clone(), *spent_by_txid)
+ });
+ Some(FullTxOut {
+ chain_position: pos.clone(),
+ outpoint: op,
+ txout: txout.clone(),
+ spent_by,
+ is_on_coinbase: tx.is_coinbase(),
+ })
+ }
+
+ /// Ordered transactions.
+ pub fn txs(
+ &self,
+ ) -> impl ExactSizeIterator<Item = CanonicalViewTx<A>> + DoubleEndedIterator + '_ {
+ self.order.iter().map(|&txid| {
+ let (tx, pos) = self.txs[&txid].clone();
+ CanonicalViewTx { pos, txid, tx }
+ })
+ }
+
+ /// Get a filtered list of outputs from the given `outpoints`.
+ ///
+ /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
+ /// (`O`) for convenience. If `O` is not necessary, the caller can use `()`, or
+ /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
+ pub fn filter_outpoints<'v, O: Clone + 'v>(
+ &'v self,
+ outpoints: impl IntoIterator<Item = (O, OutPoint)> + 'v,
+ ) -> impl Iterator<Item = (O, FullTxOut<A>)> + 'v {
+ outpoints
+ .into_iter()
+ .filter_map(|(op_i, op)| Some((op_i, self.txout(op)?)))
+ }
+
+ /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints`
+ ///
+ /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
+ /// (`O`) for convenience. If `O` is not necessary, the caller can use `()`, or
+ /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
+ pub fn filter_unspent_outpoints<'v, O: Clone + 'v>(
+ &'v self,
+ outpoints: impl IntoIterator<Item = (O, OutPoint)> + 'v,
+ ) -> impl Iterator<Item = (O, FullTxOut<A>)> + 'v {
+ self.filter_outpoints(outpoints)
+ .filter(|(_, txo)| txo.spent_by.is_none())
+ }
+
+ /// Get the total balance of `outpoints`.
+ ///
+ /// 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
+ /// (`O`) for convenience. If `O` is not necessary, the caller can use `()`, or
+ /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
+ pub fn balance<'v, O: Clone + 'v>(
+ &'v self,
+ outpoints: impl IntoIterator<Item = (O, OutPoint)> + 'v,
+ mut trust_predicate: impl FnMut(&O, ScriptBuf) -> bool,
+ ) -> Balance {
+ let mut immature = Amount::ZERO;
+ let mut trusted_pending = Amount::ZERO;
+ let mut untrusted_pending = Amount::ZERO;
+ let mut confirmed = Amount::ZERO;
+
+ for (spk_i, txout) in self.filter_unspent_outpoints(outpoints) {
+ match &txout.chain_position {
+ ChainPosition::Confirmed { .. } => {
+ if txout.is_confirmed_and_spendable(self.tip.height) {
+ confirmed += txout.txout.value;
+ } else if !txout.is_mature(self.tip.height) {
+ immature += txout.txout.value;
+ }
+ }
+ ChainPosition::Unconfirmed { .. } => {
+ if trust_predicate(&spk_i, txout.txout.script_pubkey) {
+ trusted_pending += txout.txout.value;
+ } else {
+ untrusted_pending += txout.txout.value;
+ }
+ }
+ }
+ }
+
+ Balance {
+ immature,
+ trusted_pending,
+ untrusted_pending,
+ confirmed,
+ }
+ }
+
+ /// List txids that are expected to exist under the given spks.
+ ///
+ /// This is used to fill
+ /// [`SyncRequestBuilder::expected_spk_txids`](bdk_core::spk_client::SyncRequestBuilder::expected_spk_txids).
+ ///
+ ///
+ /// The spk index range can be constrained with `range`.
+ pub fn list_expected_spk_txids<'v, I>(
+ &'v self,
+ indexer: &'v impl AsRef<SpkTxOutIndex<I>>,
+ spk_index_range: impl RangeBounds<I> + 'v,
+ ) -> impl Iterator<Item = (ScriptBuf, Txid)> + 'v
+ where
+ I: fmt::Debug + Clone + Ord + 'v,
+ {
+ let indexer = indexer.as_ref();
+ self.txs().flat_map(move |c_tx| -> Vec<_> {
+ let range = &spk_index_range;
+ let relevant_spks = indexer.relevant_spks_of_tx(&c_tx.tx);
+ relevant_spks
+ .into_iter()
+ .filter(|(i, _)| range.contains(i))
+ .map(|(_, spk)| (spk, c_tx.txid))
+ .collect()
+ })
+ }
+}
//! Contains the [`IndexedTxGraph`] and associated types. Refer to the
//! [`IndexedTxGraph`] documentation for more.
-use core::{
- convert::Infallible,
- fmt::{self, Debug},
- ops::RangeBounds,
-};
+use core::{convert::Infallible, fmt::Debug};
use alloc::{sync::Arc, vec::Vec};
-use bitcoin::{Block, OutPoint, ScriptBuf, Transaction, TxOut, Txid};
+use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
use crate::{
- spk_txout::SpkTxOutIndex,
tx_graph::{self, TxGraph},
- Anchor, BlockId, ChainOracle, Indexer, Merge, TxPosInBlock,
+ Anchor, BlockId, CanonicalView, CanonicalizationParams, ChainOracle, Indexer, Merge,
+ TxPosInBlock,
};
/// A [`TxGraph<A>`] paired with an indexer `I`, enforcing that every insertion into the graph is
where
A: Anchor,
{
- /// List txids that are expected to exist under the given spks.
- ///
- /// This is used to fill
- /// [`SyncRequestBuilder::expected_spk_txids`](bdk_core::spk_client::SyncRequestBuilder::expected_spk_txids).
- ///
- ///
- /// The spk index range can be contrained with `range`.
- ///
- /// # Error
- ///
- /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
- /// returned item.
- ///
- /// If the [`ChainOracle`] is infallible,
- /// [`list_expected_spk_txids`](Self::list_expected_spk_txids) can be used instead.
- pub fn try_list_expected_spk_txids<'a, C, I>(
+ /// Returns a [`CanonicalView`].
+ pub fn try_canonical_view<'a, C: ChainOracle>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
- spk_index_range: impl RangeBounds<I> + 'a,
- ) -> impl Iterator<Item = Result<(ScriptBuf, Txid), C::Error>> + 'a
- where
- C: ChainOracle,
- X: AsRef<SpkTxOutIndex<I>> + 'a,
- I: fmt::Debug + Clone + Ord + 'a,
- {
- self.graph
- .try_list_expected_spk_txids(chain, chain_tip, &self.index, spk_index_range)
+ params: CanonicalizationParams,
+ ) -> Result<CanonicalView<A>, C::Error> {
+ self.graph.try_canonical_view(chain, chain_tip, params)
}
- /// List txids that are expected to exist under the given spks.
+ /// Returns a [`CanonicalView`].
///
- /// This is the infallible version of
- /// [`try_list_expected_spk_txids`](Self::try_list_expected_spk_txids).
- pub fn list_expected_spk_txids<'a, C, I>(
+ /// This is the infallible version of [`try_canonical_view`](Self::try_canonical_view).
+ pub fn canonical_view<'a, C: ChainOracle<Error = Infallible>>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
- spk_index_range: impl RangeBounds<I> + 'a,
- ) -> impl Iterator<Item = (ScriptBuf, Txid)> + 'a
- where
- C: ChainOracle<Error = Infallible>,
- X: AsRef<SpkTxOutIndex<I>> + 'a,
- I: fmt::Debug + Clone + Ord + 'a,
- {
- self.try_list_expected_spk_txids(chain, chain_tip, spk_index_range)
- .map(|r| r.expect("infallible"))
+ params: CanonicalizationParams,
+ ) -> CanonicalView<A> {
+ self.graph.canonical_view(chain, chain_tip, params)
}
}
pub use chain_oracle::*;
mod canonical_iter;
pub use canonical_iter::*;
+mod canonical_view;
+pub use canonical_view::*;
#[doc(hidden)]
pub mod example_utils;
//! [`insert_txout`]: TxGraph::insert_txout
use crate::collections::*;
-use crate::spk_txout::SpkTxOutIndex;
use crate::BlockId;
use crate::CanonicalIter;
-use crate::CanonicalReason;
+use crate::CanonicalView;
use crate::CanonicalizationParams;
-use crate::ObservedIn;
-use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge};
+use crate::{Anchor, ChainOracle, ChainPosition, Merge};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use alloc::vec::Vec;
use bdk_core::ConfirmationBlockTime;
pub use bdk_core::TxUpdate;
-use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
+use bitcoin::{Amount, OutPoint, SignedAmount, Transaction, TxOut, Txid};
use core::fmt::{self, Formatter};
-use core::ops::RangeBounds;
use core::{
convert::Infallible,
ops::{Deref, RangeInclusive},
}
impl<A: Anchor> TxGraph<A> {
- /// List graph transactions that are in `chain` with `chain_tip`.
- ///
- /// 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_canonical_txs`] can be used instead.
- ///
- /// [`list_canonical_txs`]: Self::list_canonical_txs
- pub fn try_list_canonical_txs<'a, C: ChainOracle + 'a>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
- fn find_direct_anchor<A: Anchor, C: ChainOracle>(
- tx_node: &TxNode<'_, Arc<Transaction>, A>,
- chain: &C,
- chain_tip: BlockId,
- ) -> Result<Option<A>, C::Error> {
- tx_node
- .anchors
- .iter()
- .find_map(|a| -> Option<Result<A, C::Error>> {
- match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
- Ok(Some(true)) => Some(Ok(a.clone())),
- Ok(Some(false)) | Ok(None) => None,
- Err(err) => Some(Err(err)),
- }
- })
- .transpose()
- }
- self.canonical_iter(chain, chain_tip, params)
- .flat_map(move |res| {
- res.map(|(txid, _, canonical_reason)| {
- let tx_node = self.get_tx_node(txid).expect("must contain tx");
- let chain_position = match canonical_reason {
- CanonicalReason::Assumed { descendant } => match descendant {
- Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
- Some(anchor) => ChainPosition::Confirmed {
- anchor,
- transitively: None,
- },
- None => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: tx_node.last_seen,
- },
- },
- None => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: tx_node.last_seen,
- },
- },
- CanonicalReason::Anchor { anchor, descendant } => match descendant {
- Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
- Some(anchor) => ChainPosition::Confirmed {
- anchor,
- transitively: None,
- },
- None => ChainPosition::Confirmed {
- anchor,
- transitively: descendant,
- },
- },
- None => ChainPosition::Confirmed {
- anchor,
- transitively: None,
- },
- },
- CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
- ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: Some(last_seen),
- },
- ObservedIn::Block(_) => ChainPosition::Unconfirmed {
- first_seen: tx_node.first_seen,
- last_seen: None,
- },
- },
- };
- Ok(CanonicalTx {
- chain_position,
- tx_node,
- })
- })
- })
- }
-
- /// List graph transactions that are in `chain` with `chain_tip`.
- ///
- /// This is the infallible version of [`try_list_canonical_txs`].
- ///
- /// [`try_list_canonical_txs`]: Self::try_list_canonical_txs
- pub fn list_canonical_txs<'a, C: ChainOracle<Error = Infallible> + 'a>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- ) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
- self.try_list_canonical_txs(chain, chain_tip, params)
- .map(|res| res.expect("infallible"))
- }
-
- /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
- /// `chain_tip`.
- ///
- /// `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 (i.e., outputs for which we don't have the full transaction in the graph)
- /// 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,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
- ) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
- let mut canon_txs = HashMap::<Txid, CanonicalTx<Arc<Transaction>, A>>::new();
- let mut canon_spends = HashMap::<OutPoint, Txid>::new();
- for r in self.try_list_canonical_txs(chain, chain_tip, params) {
- let canonical_tx = r?;
- let txid = canonical_tx.tx_node.txid;
-
- if !canonical_tx.tx_node.tx.is_coinbase() {
- for txin in &canonical_tx.tx_node.tx.input {
- let _res = canon_spends.insert(txin.previous_output, txid);
- assert!(_res.is_none(), "tried to replace {_res:?} with {txid:?}",);
- }
- }
- canon_txs.insert(txid, canonical_tx);
- }
- Ok(outpoints.into_iter().filter_map(move |(spk_i, outpoint)| {
- let canon_tx = canon_txs.get(&outpoint.txid)?;
- let txout = canon_tx
- .tx_node
- .tx
- .output
- .get(outpoint.vout as usize)
- .cloned()?;
- let chain_position = canon_tx.chain_position.clone();
- let spent_by = canon_spends.get(&outpoint).map(|spend_txid| {
- let spend_tx = canon_txs
- .get(spend_txid)
- .cloned()
- .expect("must be canonical");
- (spend_tx.chain_position, *spend_txid)
- });
- let is_on_coinbase = canon_tx.tx_node.is_coinbase();
- Some((
- spk_i,
- FullTxOut {
- outpoint,
- txout,
- chain_position,
- spent_by,
- is_on_coinbase,
- },
- ))
- }))
- }
-
/// List txids by descending anchor height order.
///
/// If multiple anchors exist for a txid, the highest anchor height will be used. Transactions
CanonicalIter::new(self, chain, chain_tip, params)
}
- /// 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`].
- ///
- /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts
- pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
- ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
- self.try_filter_chain_txouts(chain, chain_tip, params, outpoints)
- .expect("oracle is infallible")
- }
-
- /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
- /// `chain` with `chain_tip`.
- ///
- /// `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>(
+ /// Returns a [`CanonicalView`].
+ pub fn try_canonical_view<'a, C: ChainOracle>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
- outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
- ) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
- Ok(self
- .try_filter_chain_txouts(chain, chain_tip, params, outpoints)?
- .filter(|(_, full_txo)| full_txo.spent_by.is_none()))
+ ) -> Result<CanonicalView<A>, C::Error> {
+ CanonicalView::new(self, chain, chain_tip, params)
}
- /// 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`].
- ///
- /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents
- pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
+ /// Returns a [`CanonicalView`].
+ pub fn canonical_view<'a, C: ChainOracle<Error = Infallible>>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
- txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
- ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
- self.try_filter_chain_unspents(chain, chain_tip, params, txouts)
- .expect("oracle is infallible")
- }
-
- /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
- ///
- /// 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<C: ChainOracle, OI: Clone>(
- &self,
- chain: &C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
- mut trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
- ) -> Result<Balance, C::Error> {
- let mut immature = Amount::ZERO;
- let mut trusted_pending = Amount::ZERO;
- let mut untrusted_pending = Amount::ZERO;
- let mut confirmed = Amount::ZERO;
-
- for (spk_i, txout) in self.try_filter_chain_unspents(chain, chain_tip, params, outpoints)? {
- 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;
- }
- }
- ChainPosition::Unconfirmed { .. } => {
- if trust_predicate(&spk_i, txout.txout.script_pubkey) {
- trusted_pending += txout.txout.value;
- } else {
- untrusted_pending += txout.txout.value;
- }
- }
- }
- }
-
- Ok(Balance {
- immature,
- trusted_pending,
- untrusted_pending,
- confirmed,
- })
- }
-
- /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
- ///
- /// This is the infallible version of [`try_balance`].
- ///
- /// ### Minimum confirmations
- ///
- /// To filter for transactions with at least `N` confirmations, pass a `chain_tip` that is
- /// `N - 1` blocks below the actual tip. This ensures that only transactions with at least `N`
- /// confirmations are counted as confirmed in the returned [`Balance`].
- ///
- /// ```
- /// # use bdk_chain::tx_graph::TxGraph;
- /// # use bdk_chain::{local_chain::LocalChain, CanonicalizationParams, ConfirmationBlockTime};
- /// # use bdk_testenv::{hash, utils::new_tx};
- /// # use bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
- ///
- /// # let spk = ScriptBuf::from_hex("0014c692ecf13534982a9a2834565cbd37add8027140").unwrap();
- /// # let chain =
- /// # LocalChain::<BlockHash>::from_blocks((0..=15).map(|i| (i as u32, hash!("h"))).collect()).unwrap();
- /// # let mut graph: TxGraph = TxGraph::default();
- /// # let coinbase_tx = Transaction {
- /// # input: vec![TxIn {
- /// # previous_output: OutPoint::null(),
- /// # ..Default::default()
- /// # }],
- /// # output: vec![TxOut {
- /// # value: Amount::from_sat(70000),
- /// # script_pubkey: spk.clone(),
- /// # }],
- /// # ..new_tx(0)
- /// # };
- /// # let tx = Transaction {
- /// # input: vec![TxIn {
- /// # previous_output: OutPoint::new(coinbase_tx.compute_txid(), 0),
- /// # ..Default::default()
- /// # }],
- /// # output: vec![TxOut {
- /// # value: Amount::from_sat(42_000),
- /// # script_pubkey: spk.clone(),
- /// # }],
- /// # ..new_tx(1)
- /// # };
- /// # let txid = tx.compute_txid();
- /// # let _ = graph.insert_tx(tx.clone());
- /// # let _ = graph.insert_anchor(
- /// # txid,
- /// # ConfirmationBlockTime {
- /// # block_id: chain.get(10).unwrap().block_id(),
- /// # confirmation_time: 123456,
- /// # },
- /// # );
- ///
- /// let minimum_confirmations = 6;
- /// let target_tip = chain
- /// .tip()
- /// .floor_below(minimum_confirmations - 1)
- /// .expect("checkpoint from local chain must have genesis");
- /// let balance = graph.balance(
- /// &chain,
- /// target_tip.block_id(),
- /// CanonicalizationParams::default(),
- /// std::iter::once(((), OutPoint::new(txid, 0))),
- /// |_: &(), _| true,
- /// );
- /// assert_eq!(balance.confirmed, Amount::from_sat(42_000));
- /// ```
- ///
- /// [`try_balance`]: Self::try_balance
- pub fn balance<C: ChainOracle<Error = Infallible>, OI: Clone>(
- &self,
- chain: &C,
- chain_tip: BlockId,
- params: CanonicalizationParams,
- outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
- trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
- ) -> Balance {
- self.try_balance(chain, chain_tip, params, outpoints, trust_predicate)
- .expect("oracle is infallible")
- }
-
- /// List txids that are expected to exist under the given spks.
- ///
- /// This is used to fill
- /// [`SyncRequestBuilder::expected_spk_txids`](bdk_core::spk_client::SyncRequestBuilder::expected_spk_txids).
- ///
- ///
- /// The spk index range can be constrained with `range`.
- ///
- /// # Error
- ///
- /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
- /// returned item.
- ///
- /// If the [`ChainOracle`] is infallible,
- /// [`list_expected_spk_txids`](Self::list_expected_spk_txids) can be used instead.
- pub fn try_list_expected_spk_txids<'a, C, I>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- indexer: &'a impl AsRef<SpkTxOutIndex<I>>,
- spk_index_range: impl RangeBounds<I> + 'a,
- ) -> impl Iterator<Item = Result<(ScriptBuf, Txid), C::Error>> + 'a
- where
- C: ChainOracle,
- I: fmt::Debug + Clone + Ord + 'a,
- {
- let indexer = indexer.as_ref();
- self.try_list_canonical_txs(chain, chain_tip, CanonicalizationParams::default())
- .flat_map(move |res| -> Vec<Result<(ScriptBuf, Txid), C::Error>> {
- let range = &spk_index_range;
- let c_tx = match res {
- Ok(c_tx) => c_tx,
- Err(err) => return vec![Err(err)],
- };
- let relevant_spks = indexer.relevant_spks_of_tx(&c_tx.tx_node);
- relevant_spks
- .into_iter()
- .filter(|(i, _)| range.contains(i))
- .map(|(_, spk)| Ok((spk, c_tx.tx_node.txid)))
- .collect()
- })
- }
-
- /// List txids that are expected to exist under the given spks.
- ///
- /// This is the infallible version of
- /// [`try_list_expected_spk_txids`](Self::try_list_expected_spk_txids).
- pub fn list_expected_spk_txids<'a, C, I>(
- &'a self,
- chain: &'a C,
- chain_tip: BlockId,
- indexer: &'a impl AsRef<SpkTxOutIndex<I>>,
- spk_index_range: impl RangeBounds<I> + 'a,
- ) -> impl Iterator<Item = (ScriptBuf, Txid)> + 'a
- where
- C: ChainOracle<Error = Infallible>,
- I: fmt::Debug + Clone + Ord + 'a,
- {
- self.try_list_expected_spk_txids(chain, chain_tip, indexer, spk_index_range)
- .map(|r| r.expect("infallible"))
+ ) -> CanonicalView<A> {
+ CanonicalView::new(self, chain, chain_tip, params).expect("infallible")
}
/// Construct a `TxGraph` from a `changeset`.
.map(|cp| cp.block_id())
.unwrap_or_else(|| panic!("block must exist at {height}"));
let txouts = graph
- .graph()
- .filter_chain_txouts(
- &local_chain,
- chain_tip,
- CanonicalizationParams::default(),
- graph.index.outpoints().iter().cloned(),
- )
+ .canonical_view(&local_chain, chain_tip, CanonicalizationParams::default())
+ .filter_outpoints(graph.index.outpoints().iter().cloned())
.collect::<Vec<_>>();
let utxos = graph
- .graph()
- .filter_chain_unspents(
- &local_chain,
- chain_tip,
- CanonicalizationParams::default(),
- graph.index.outpoints().iter().cloned(),
- )
+ .canonical_view(&local_chain, chain_tip, CanonicalizationParams::default())
+ .filter_unspent_outpoints(graph.index.outpoints().iter().cloned())
.collect::<Vec<_>>();
- let balance = graph.graph().balance(
- &local_chain,
- chain_tip,
- CanonicalizationParams::default(),
- graph.index.outpoints().iter().cloned(),
- |_, spk: ScriptBuf| trusted_spks.contains(&spk),
- );
+ let balance = graph
+ .canonical_view(&local_chain, chain_tip, CanonicalizationParams::default())
+ .balance(
+ graph.index.outpoints().iter().cloned(),
+ |_, spk: ScriptBuf| trusted_spks.contains(&spk),
+ );
let confirmed_txouts_txid = txouts
.iter()
// check chain position
let chain_pos = graph
- .graph()
- .list_canonical_txs(
+ .canonical_view(
chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
)
+ .txs()
.find_map(|canon_tx| {
- if canon_tx.tx_node.txid == txid {
- Some(canon_tx.chain_position)
+ if canon_tx.txid == txid {
+ Some(canon_tx.pos)
} else {
None
}
let build_canonical_spends =
|chain: &LocalChain, tx_graph: &TxGraph<ConfirmationBlockTime>| -> HashMap<OutPoint, _> {
tx_graph
- .filter_chain_txouts(
- chain,
- tip.block_id(),
- CanonicalizationParams::default(),
- tx_graph.all_txouts().map(|(op, _)| ((), op)),
- )
+ .canonical_view(chain, tip.block_id(), CanonicalizationParams::default())
+ .filter_outpoints(tx_graph.all_txouts().map(|(op, _)| ((), op)))
.filter_map(|(_, full_txo)| Some((full_txo.outpoint, full_txo.spent_by?)))
.collect()
};
tx_graph: &TxGraph<ConfirmationBlockTime>|
-> HashMap<Txid, ChainPosition<ConfirmationBlockTime>> {
tx_graph
- .list_canonical_txs(chain, tip.block_id(), CanonicalizationParams::default())
- .map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position))
+ .canonical_view(chain, tip.block_id(), CanonicalizationParams::default())
+ .txs()
+ .map(|canon_tx| (canon_tx.txid, canon_tx.pos))
.collect()
};
.collect();
let chain = LocalChain::from_blocks(blocks).unwrap();
let canonical_txs: Vec<_> = graph
- .list_canonical_txs(
+ .canonical_view(
&chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
)
+ .txs()
.collect();
assert!(canonical_txs.is_empty());
// tx0 with seen_at should be returned by canonical txs
let _ = graph.insert_seen_at(txids[0], 2);
- let mut canonical_txs = graph.list_canonical_txs(
+ let canonical_view = graph.canonical_view(
&chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
);
- assert_eq!(
- canonical_txs.next().map(|tx| tx.tx_node.txid).unwrap(),
- txids[0]
- );
+ let mut canonical_txs = canonical_view.txs();
+ assert_eq!(canonical_txs.next().map(|tx| tx.txid).unwrap(), txids[0]);
drop(canonical_txs);
// tx1 with anchor is also canonical
let _ = graph.insert_anchor(txids[1], block_id!(2, "B"));
let canonical_txids: Vec<_> = graph
- .list_canonical_txs(
+ .canonical_view(
&chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
)
- .map(|tx| tx.tx_node.txid)
+ .txs()
+ .map(|tx| tx.txid)
.collect();
assert!(canonical_txids.contains(&txids[1]));
assert!(graph.txs_with_no_anchor_or_last_seen().next().is_none());
let txs = env
.tx_graph
- .list_canonical_txs(&local_chain, chain_tip, env.canonicalization_params.clone())
- .map(|tx| tx.tx_node.txid)
+ .canonical_view(&local_chain, chain_tip, env.canonicalization_params.clone())
+ .txs()
+ .map(|tx| tx.txid)
.collect::<BTreeSet<_>>();
let exp_txs = scenario
.exp_chain_txs
let txouts = env
.tx_graph
- .filter_chain_txouts(
- &local_chain,
- chain_tip,
- env.canonicalization_params.clone(),
- env.indexer.outpoints().iter().cloned(),
- )
+ .canonical_view(&local_chain, chain_tip, env.canonicalization_params.clone())
+ .filter_outpoints(env.indexer.outpoints().iter().cloned())
.map(|(_, full_txout)| full_txout.outpoint)
.collect::<BTreeSet<_>>();
let exp_txouts = scenario
let utxos = env
.tx_graph
- .filter_chain_unspents(
- &local_chain,
- chain_tip,
- env.canonicalization_params.clone(),
- env.indexer.outpoints().iter().cloned(),
- )
+ .canonical_view(&local_chain, chain_tip, env.canonicalization_params.clone())
+ .filter_unspent_outpoints(env.indexer.outpoints().iter().cloned())
.map(|(_, full_txout)| full_txout.outpoint)
.collect::<BTreeSet<_>>();
let exp_utxos = scenario
scenario.name
);
- let balance = env.tx_graph.balance(
- &local_chain,
- chain_tip,
- env.canonicalization_params.clone(),
- env.indexer.outpoints().iter().cloned(),
- |_, spk: ScriptBuf| env.indexer.index_of_spk(spk).is_some(),
- );
+ let balance = env
+ .tx_graph
+ .canonical_view(&local_chain, chain_tip, env.canonicalization_params.clone())
+ .balance(
+ env.indexer.outpoints().iter().cloned(),
+ |_, spk: ScriptBuf| env.indexer.index_of_spk(spk).is_some(),
+ );
assert_eq!(
balance, scenario.exp_balance,
"\n[{}] 'balance' failed",
) -> anyhow::Result<Balance> {
let chain_tip = recv_chain.tip().block_id();
let outpoints = recv_graph.index.outpoints().clone();
- let balance = recv_graph.graph().balance(
- recv_chain,
- chain_tip,
- CanonicalizationParams::default(),
- outpoints,
- |_, _| true,
- );
+ let balance = recv_graph
+ .canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
+ .balance(outpoints, |_, _| true);
Ok(balance)
}
let sync_request = SyncRequest::builder()
.chain_tip(chain.tip())
.spks_with_indexes(graph.index.all_spks().clone())
- .expected_spk_txids(graph.list_expected_spk_txids(&chain, chain.tip().block_id(), ..));
+ .expected_spk_txids(
+ graph
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .list_expected_spk_txids(&graph.index, ..),
+ );
let sync_response = client.sync(sync_request, BATCH_SIZE, true)?;
assert!(
sync_response
let sync_request = SyncRequest::builder()
.chain_tip(chain.tip())
.spks_with_indexes(graph.index.all_spks().clone())
- .expected_spk_txids(graph.list_expected_spk_txids(&chain, chain.tip().block_id(), ..));
+ .expected_spk_txids(
+ graph
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .list_expected_spk_txids(&graph.index, ..),
+ );
let sync_response = client.sync(sync_request, BATCH_SIZE, true)?;
assert!(
sync_response
let sync_request = SyncRequest::builder()
.chain_tip(chain.tip())
.spks_with_indexes(graph.index.all_spks().clone())
- .expected_spk_txids(graph.list_expected_spk_txids(&chain, chain.tip().block_id(), ..));
+ .expected_spk_txids(
+ graph
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .list_expected_spk_txids(&graph.index, ..),
+ );
let sync_response = client.sync(sync_request, 1).await?;
assert!(
sync_response
let sync_request = SyncRequest::builder()
.chain_tip(chain.tip())
.spks_with_indexes(graph.index.all_spks().clone())
- .expected_spk_txids(graph.list_expected_spk_txids(&chain, chain.tip().block_id(), ..));
+ .expected_spk_txids(
+ graph
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .list_expected_spk_txids(&graph.index, ..),
+ );
let sync_response = client.sync(sync_request, 1).await?;
assert!(
sync_response
let sync_request = SyncRequest::builder()
.chain_tip(chain.tip())
.spks_with_indexes(graph.index.all_spks().clone())
- .expected_spk_txids(graph.list_expected_spk_txids(&chain, chain.tip().block_id(), ..));
+ .expected_spk_txids(
+ graph
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .list_expected_spk_txids(&graph.index, ..),
+ );
let sync_response = client.sync(sync_request, 1)?;
assert!(
sync_response
let sync_request = SyncRequest::builder()
.chain_tip(chain.tip())
.spks_with_indexes(graph.index.all_spks().clone())
- .expected_spk_txids(graph.list_expected_spk_txids(&chain, chain.tip().block_id(), ..));
+ .expected_spk_txids(
+ graph
+ .canonical_view(&chain, chain.tip().block_id(), Default::default())
+ .list_expected_spk_txids(&graph.index, ..),
+ );
let sync_response = client.sync(sync_request, 1)?;
assert!(
sync_response
chain.tip(),
fallback_height,
graph
- .graph()
- .list_canonical_txs(
+ .canonical_view(
&*chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
)
- .filter(|tx| tx.chain_position.is_unconfirmed()),
+ .txs()
+ .filter(|tx| tx.pos.is_unconfirmed())
+ .map(|tx| tx.tx),
)
};
let mut db_stage = ChangeSet::default();
last_print = Instant::now();
let synced_to = chain.tip();
let balance = {
- graph.graph().balance(
- &*chain,
- synced_to.block_id(),
- CanonicalizationParams::default(),
- graph.index.outpoints().iter().cloned(),
- |(k, _), _| k == &Keychain::Internal,
- )
+ graph
+ .canonical_view(
+ &*chain,
+ synced_to.block_id(),
+ CanonicalizationParams::default(),
+ )
+ .balance(graph.index.outpoints().iter().cloned(), |(k, _), _| {
+ k == &Keychain::Internal
+ })
};
println!(
"[{:>10}s] synced to {} @ {} | total: {}",
chain.tip(),
fallback_height,
graph
- .graph()
- .list_canonical_txs(
+ .canonical_view(
&*chain,
chain.tip().block_id(),
CanonicalizationParams::default(),
)
- .filter(|tx| tx.chain_position.is_unconfirmed()),
+ .txs()
+ .filter(|tx| tx.pos.is_unconfirmed())
+ .map(|tx| tx.tx),
)
};
last_print = Some(Instant::now());
let synced_to = chain.tip();
let balance = {
- graph.graph().balance(
- &*chain,
- synced_to.block_id(),
- CanonicalizationParams::default(),
- graph.index.outpoints().iter().cloned(),
- |(k, _), _| k == &Keychain::Internal,
- )
+ graph
+ .canonical_view(
+ &*chain,
+ synced_to.block_id(),
+ CanonicalizationParams::default(),
+ )
+ .balance(graph.index.outpoints().iter().cloned(), |(k, _), _| {
+ k == &Keychain::Internal
+ })
};
println!(
"[{:>10}s] synced to {} @ {} / {} | total: {}",
let chain_tip = chain.get_chain_tip()?;
let outpoints = graph.index.outpoints();
graph
- .graph()
- .try_filter_chain_unspents(
- chain,
- chain_tip,
- CanonicalizationParams::default(),
- outpoints.iter().cloned(),
- )?
+ .try_canonical_view(chain, chain_tip, CanonicalizationParams::default())?
+ .filter_unspent_outpoints(outpoints.iter().cloned())
.filter_map(|((k, i), full_txo)| -> Option<Result<PlanUtxo, _>> {
let desc = graph
.index
}
}
- let balance = graph.graph().try_balance(
- chain,
- chain.get_chain_tip()?,
- CanonicalizationParams::default(),
- graph.index.outpoints().iter().cloned(),
- |(k, _), _| k == &Keychain::Internal,
- )?;
+ let balance = graph
+ .try_canonical_view(
+ chain,
+ chain.get_chain_tip()?,
+ CanonicalizationParams::default(),
+ )?
+ .balance(graph.index.outpoints().iter().cloned(), |(k, _), _| {
+ k == &Keychain::Internal
+ });
let confirmed_total = balance.confirmed + balance.immature;
let unconfirmed_total = balance.untrusted_pending + balance.trusted_pending;
unconfirmed,
} => {
let txouts = graph
- .graph()
- .try_filter_chain_txouts(
- chain,
- chain_tip,
- CanonicalizationParams::default(),
- outpoints.iter().cloned(),
- )?
+ .try_canonical_view(chain, chain_tip, CanonicalizationParams::default())?
+ .filter_outpoints(outpoints.iter().cloned())
.filter(|(_, full_txo)| match (spent, unspent) {
(true, false) => full_txo.spent_by.is_some(),
(false, true) => full_txo.spent_by.is_none(),
eprintln!("[ SCANNING {pc:03.0}% ] {item}");
});
- request = request.expected_spk_txids(graph.list_expected_spk_txids(
+ let canonical_view = graph.canonical_view(
&*chain,
chain_tip.block_id(),
- ..,
- ));
+ CanonicalizationParams::default(),
+ );
+
+ request = request
+ .expected_spk_txids(canonical_view.list_expected_spk_txids(&graph.index, ..));
if all_spks {
request = request.spks_with_indexes(graph.index.revealed_spks(..));
}
if utxos {
let init_outpoints = graph.index.outpoints();
request = request.outpoints(
- graph
- .graph()
- .filter_chain_unspents(
- &*chain,
- chain_tip.block_id(),
- CanonicalizationParams::default(),
- init_outpoints.iter().cloned(),
- )
+ canonical_view
+ .filter_unspent_outpoints(init_outpoints.iter().cloned())
.map(|(_, utxo)| utxo.outpoint),
);
};
if unconfirmed {
request = request.txids(
- graph
- .graph()
- .list_canonical_txs(
- &*chain,
- chain_tip.block_id(),
- CanonicalizationParams::default(),
- )
- .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
- .map(|canonical_tx| canonical_tx.tx_node.txid),
+ canonical_view
+ .txs()
+ .filter(|canonical_tx| !canonical_tx.pos.is_confirmed())
+ .map(|canonical_tx| canonical_tx.txid),
);
}
{
let graph = graph.lock().unwrap();
let chain = chain.lock().unwrap();
- request = request.expected_spk_txids(graph.list_expected_spk_txids(
+ let canonical_view = graph.canonical_view(
&*chain,
local_tip.block_id(),
- ..,
- ));
+ CanonicalizationParams::default(),
+ );
+
+ request = request
+ .expected_spk_txids(canonical_view.list_expected_spk_txids(&graph.index, ..));
if *all_spks {
request = request.spks_with_indexes(graph.index.revealed_spks(..));
}
// `EsploraExt::update_tx_graph_without_keychain`.
let init_outpoints = graph.index.outpoints();
request = request.outpoints(
- graph
- .graph()
- .filter_chain_unspents(
- &*chain,
- local_tip.block_id(),
- CanonicalizationParams::default(),
- init_outpoints.iter().cloned(),
- )
+ canonical_view
+ .filter_unspent_outpoints(init_outpoints.iter().cloned())
.map(|(_, utxo)| utxo.outpoint),
);
};
// We provide the unconfirmed txids to
// `EsploraExt::update_tx_graph_without_keychain`.
request = request.txids(
- graph
- .graph()
- .list_canonical_txs(
- &*chain,
- local_tip.block_id(),
- CanonicalizationParams::default(),
- )
- .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
- .map(|canonical_tx| canonical_tx.tx_node.txid),
+ canonical_view
+ .txs()
+ .filter(|canonical_tx| !canonical_tx.pos.is_confirmed())
+ .map(|canonical_tx| canonical_tx.txid),
);
}
}