Improve txout
listing and balance APIs for redesigned structures
Instead of relying on a `OwnedIndexer` trait to filter for relevant txouts, we move the listing and balance methods from `IndexedTxGraph` to `TxGraph` and add an additional input (list of relevant outpoints) to these methods. This provides a simpler implementation and a more flexible API.
This commit is contained in:
parent
4963240599
commit
e01d17d59b
@ -1,12 +1,9 @@
|
|||||||
use core::convert::Infallible;
|
|
||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
use bitcoin::{OutPoint, Transaction, TxOut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
keychain::Balance,
|
|
||||||
tx_graph::{Additions, TxGraph},
|
tx_graph::{Additions, TxGraph},
|
||||||
Anchor, Append, BlockId, ChainOracle, FullTxOut, ObservedAs,
|
Anchor, Append,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
|
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
|
||||||
@ -29,6 +26,14 @@ impl<A, I: Default> Default for IndexedTxGraph<A, I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<A, I> IndexedTxGraph<A, I> {
|
impl<A, I> IndexedTxGraph<A, I> {
|
||||||
|
/// Construct a new [`IndexedTxGraph`] with a given `index`.
|
||||||
|
pub fn new(index: I) -> Self {
|
||||||
|
Self {
|
||||||
|
index,
|
||||||
|
graph: TxGraph::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a reference of the internal transaction graph.
|
/// Get a reference of the internal transaction graph.
|
||||||
pub fn graph(&self) -> &TxGraph<A> {
|
pub fn graph(&self) -> &TxGraph<A> {
|
||||||
&self.graph
|
&self.graph
|
||||||
@ -157,115 +162,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Anchor, I: OwnedIndexer> IndexedTxGraph<A, I> {
|
|
||||||
pub fn try_list_owned_txouts<'a, C: ChainOracle + 'a>(
|
|
||||||
&'a self,
|
|
||||||
chain: &'a C,
|
|
||||||
chain_tip: BlockId,
|
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
|
|
||||||
self.graph()
|
|
||||||
.try_list_chain_txouts(chain, chain_tip)
|
|
||||||
.filter(|r| {
|
|
||||||
if let Ok(full_txout) = r {
|
|
||||||
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_owned_txouts<'a, C: ChainOracle<Error = Infallible> + 'a>(
|
|
||||||
&'a self,
|
|
||||||
chain: &'a C,
|
|
||||||
chain_tip: BlockId,
|
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
|
|
||||||
self.try_list_owned_txouts(chain, chain_tip)
|
|
||||||
.map(|r| r.expect("oracle is infallible"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_list_owned_unspents<'a, C: ChainOracle + 'a>(
|
|
||||||
&'a self,
|
|
||||||
chain: &'a C,
|
|
||||||
chain_tip: BlockId,
|
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
|
|
||||||
self.graph()
|
|
||||||
.try_list_chain_unspents(chain, chain_tip)
|
|
||||||
.filter(|r| {
|
|
||||||
if let Ok(full_txout) = r {
|
|
||||||
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_owned_unspents<'a, C: ChainOracle<Error = Infallible> + 'a>(
|
|
||||||
&'a self,
|
|
||||||
chain: &'a C,
|
|
||||||
chain_tip: BlockId,
|
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
|
|
||||||
self.try_list_owned_unspents(chain, chain_tip)
|
|
||||||
.map(|r| r.expect("oracle is infallible"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_balance<C, F>(
|
|
||||||
&self,
|
|
||||||
chain: &C,
|
|
||||||
chain_tip: BlockId,
|
|
||||||
mut should_trust: F,
|
|
||||||
) -> Result<Balance, C::Error>
|
|
||||||
where
|
|
||||||
C: ChainOracle,
|
|
||||||
F: FnMut(&Script) -> bool,
|
|
||||||
{
|
|
||||||
let tip_height = chain_tip.height;
|
|
||||||
|
|
||||||
let mut immature = 0;
|
|
||||||
let mut trusted_pending = 0;
|
|
||||||
let mut untrusted_pending = 0;
|
|
||||||
let mut confirmed = 0;
|
|
||||||
|
|
||||||
for res in self.try_list_owned_unspents(chain, chain_tip) {
|
|
||||||
let txout = res?;
|
|
||||||
|
|
||||||
match &txout.chain_position {
|
|
||||||
ObservedAs::Confirmed(_) => {
|
|
||||||
if txout.is_confirmed_and_spendable(tip_height) {
|
|
||||||
confirmed += txout.txout.value;
|
|
||||||
} else if !txout.is_mature(tip_height) {
|
|
||||||
immature += txout.txout.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObservedAs::Unconfirmed(_) => {
|
|
||||||
if should_trust(&txout.txout.script_pubkey) {
|
|
||||||
trusted_pending += txout.txout.value;
|
|
||||||
} else {
|
|
||||||
untrusted_pending += txout.txout.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Balance {
|
|
||||||
immature,
|
|
||||||
trusted_pending,
|
|
||||||
untrusted_pending,
|
|
||||||
confirmed,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn balance<C, F>(&self, chain: &C, chain_tip: BlockId, should_trust: F) -> Balance
|
|
||||||
where
|
|
||||||
C: ChainOracle<Error = Infallible>,
|
|
||||||
F: FnMut(&Script) -> bool,
|
|
||||||
{
|
|
||||||
self.try_balance(chain, chain_tip, should_trust)
|
|
||||||
.expect("error is infallible")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure that represents changes to an [`IndexedTxGraph`].
|
/// A structure that represents changes to an [`IndexedTxGraph`].
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@ -324,9 +220,3 @@ pub trait Indexer {
|
|||||||
/// Determines whether the transaction should be included in the index.
|
/// Determines whether the transaction should be included in the index.
|
||||||
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait that extends [`Indexer`] to also index "owned" script pubkeys.
|
|
||||||
pub trait OwnedIndexer: Indexer {
|
|
||||||
/// Determines whether a given script pubkey (`spk`) is owned.
|
|
||||||
fn is_spk_owned(&self, spk: &Script) -> bool;
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
collections::*,
|
collections::*,
|
||||||
indexed_tx_graph::{Indexer, OwnedIndexer},
|
indexed_tx_graph::Indexer,
|
||||||
miniscript::{Descriptor, DescriptorPublicKey},
|
miniscript::{Descriptor, DescriptorPublicKey},
|
||||||
spk_iter::BIP32_MAX_INDEX,
|
spk_iter::BIP32_MAX_INDEX,
|
||||||
ForEachTxOut, SpkIterator, SpkTxOutIndex,
|
ForEachTxOut, SpkIterator, SpkTxOutIndex,
|
||||||
@ -109,12 +109,6 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug> OwnedIndexer for KeychainTxOutIndex<K> {
|
|
||||||
fn is_spk_owned(&self, spk: &Script) -> bool {
|
|
||||||
self.index_of_spk(spk).is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||||
/// Scans an object for relevant outpoints, which are stored and indexed internally.
|
/// Scans an object for relevant outpoints, which are stored and indexed internally.
|
||||||
///
|
///
|
||||||
@ -153,6 +147,11 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the set of indexed outpoints.
|
||||||
|
pub fn outpoints(&self) -> &BTreeSet<((K, u32), OutPoint)> {
|
||||||
|
self.inner.outpoints()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a reference to the internal map of the keychain to descriptors.
|
/// Return a reference to the internal map of the keychain to descriptors.
|
||||||
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
|
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
|
||||||
&self.keychains
|
&self.keychains
|
||||||
|
@ -2,7 +2,7 @@ use core::ops::RangeBounds;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
|
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
|
||||||
indexed_tx_graph::{Indexer, OwnedIndexer},
|
indexed_tx_graph::Indexer,
|
||||||
ForEachTxOut,
|
ForEachTxOut,
|
||||||
};
|
};
|
||||||
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
|
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
|
||||||
@ -75,12 +75,6 @@ impl<I: Clone + Ord> Indexer for SpkTxOutIndex<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Clone + Ord + 'static> OwnedIndexer for SpkTxOutIndex<I> {
|
|
||||||
fn is_spk_owned(&self, spk: &Script) -> bool {
|
|
||||||
self.spk_indices.get(spk).is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This macro is used instead of a member function of `SpkTxOutIndex`, which would result in a
|
/// This macro is used instead of a member function of `SpkTxOutIndex`, which would result in a
|
||||||
/// compiler error[E0521]: "borrowed data escapes out of closure" when we attempt to take a
|
/// compiler error[E0521]: "borrowed data escapes out of closure" when we attempt to take a
|
||||||
/// reference out of the `ForEachTxOut` closure during scanning.
|
/// reference out of the `ForEachTxOut` closure during scanning.
|
||||||
@ -126,6 +120,11 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
|
|||||||
scan_txout!(self, op, txout)
|
scan_txout!(self, op, txout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the set of indexed outpoints.
|
||||||
|
pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> {
|
||||||
|
&self.spk_txouts
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over all known txouts that spend to tracked script pubkeys.
|
/// Iterate over all known txouts that spend to tracked script pubkeys.
|
||||||
pub fn txouts(
|
pub fn txouts(
|
||||||
&self,
|
&self,
|
||||||
|
@ -56,10 +56,11 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collections::*, Anchor, Append, BlockId, ChainOracle, ForEachTxOut, FullTxOut, ObservedAs,
|
collections::*, keychain::Balance, Anchor, Append, BlockId, ChainOracle, ForEachTxOut,
|
||||||
|
FullTxOut, ObservedAs,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
|
||||||
use core::{
|
use core::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
ops::{Deref, RangeInclusive},
|
ops::{Deref, RangeInclusive},
|
||||||
@ -762,107 +763,190 @@ impl<A: Anchor> TxGraph<A> {
|
|||||||
.map(|r| r.expect("oracle is infallible"))
|
.map(|r| r.expect("oracle is infallible"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List outputs that are in `chain` with `chain_tip`.
|
/// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
|
||||||
|
/// `chain_tip`.
|
||||||
///
|
///
|
||||||
/// Floating ouputs are not iterated over.
|
/// `outpoints` is a list of outpoints we are interested in, coupled with the associated txout's
|
||||||
|
/// script pubkey index (`S`).
|
||||||
///
|
///
|
||||||
/// The `filter_predicate` should return true for outputs that we wish to iterate over.
|
/// Floating outputs are ignored.
|
||||||
///
|
///
|
||||||
/// # Error
|
/// # Error
|
||||||
///
|
///
|
||||||
/// A returned item can error if the [`ChainOracle`] implementation (`chain`) fails.
|
/// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
|
||||||
|
/// fails.
|
||||||
///
|
///
|
||||||
/// If the [`ChainOracle`] is infallible, [`list_chain_txouts`] can be used instead.
|
/// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used
|
||||||
|
/// instead.
|
||||||
///
|
///
|
||||||
/// [`list_chain_txouts`]: Self::list_chain_txouts
|
/// [`filter_chain_txouts`]: Self::filter_chain_txouts
|
||||||
pub fn try_list_chain_txouts<'a, C: ChainOracle + 'a>(
|
pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, S: Clone + 'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
chain_tip: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
|
outpoints: impl IntoIterator<Item = (S, OutPoint)> + 'a,
|
||||||
self.try_list_chain_txs(chain, chain_tip)
|
) -> impl Iterator<Item = Result<(S, FullTxOut<ObservedAs<A>>), C::Error>> + 'a {
|
||||||
.flat_map(move |tx_res| match tx_res {
|
outpoints
|
||||||
Ok(canonical_tx) => canonical_tx
|
.into_iter()
|
||||||
.node
|
.map(
|
||||||
.output
|
move |(spk_i, op)| -> Result<Option<(S, FullTxOut<_>)>, C::Error> {
|
||||||
.iter()
|
let tx_node = match self.get_tx_node(op.txid) {
|
||||||
.enumerate()
|
Some(n) => n,
|
||||||
.map(|(vout, txout)| {
|
None => return Ok(None),
|
||||||
let outpoint = OutPoint::new(canonical_tx.node.txid, vout as _);
|
};
|
||||||
Ok((outpoint, txout.clone(), canonical_tx.clone()))
|
|
||||||
})
|
let txout = match tx_node.tx.output.get(op.vout as usize) {
|
||||||
.collect::<Vec<_>>(),
|
Some(txout) => txout.clone(),
|
||||||
Err(err) => vec![Err(err)],
|
None => return Ok(None),
|
||||||
})
|
};
|
||||||
.map(move |res| -> Result<_, C::Error> {
|
|
||||||
let (
|
let chain_position =
|
||||||
outpoint,
|
match self.try_get_chain_position(chain, chain_tip, op.txid)? {
|
||||||
txout,
|
Some(pos) => pos.cloned(),
|
||||||
CanonicalTx {
|
None => return Ok(None),
|
||||||
observed_as,
|
};
|
||||||
node: tx_node,
|
|
||||||
},
|
let spent_by = self
|
||||||
) = res?;
|
.try_get_chain_spend(chain, chain_tip, op)?
|
||||||
let chain_position = observed_as.cloned();
|
.map(|(a, txid)| (a.cloned(), txid));
|
||||||
let spent_by = self
|
|
||||||
.try_get_chain_spend(chain, chain_tip, outpoint)?
|
Ok(Some((
|
||||||
.map(|(obs_as, txid)| (obs_as.cloned(), txid));
|
spk_i,
|
||||||
let is_on_coinbase = tx_node.tx.is_coin_base();
|
FullTxOut {
|
||||||
Ok(FullTxOut {
|
outpoint: op,
|
||||||
outpoint,
|
txout,
|
||||||
txout,
|
chain_position,
|
||||||
chain_position,
|
spent_by,
|
||||||
spent_by,
|
is_on_coinbase: tx_node.tx.is_coin_base(),
|
||||||
is_on_coinbase,
|
},
|
||||||
})
|
)))
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
.filter_map(Result::transpose)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List outputs that are in `chain` with `chain_tip`.
|
/// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
|
||||||
|
/// `chain_tip`.
|
||||||
///
|
///
|
||||||
/// This is the infallible version of [`try_list_chain_txouts`].
|
/// This is the infallible version of [`try_filter_chain_txouts`].
|
||||||
///
|
///
|
||||||
/// [`try_list_chain_txouts`]: Self::try_list_chain_txouts
|
/// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts
|
||||||
pub fn list_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a>(
|
pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, S: Clone + 'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
chain_tip: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
|
outpoints: impl IntoIterator<Item = (S, OutPoint)> + 'a,
|
||||||
self.try_list_chain_txouts(chain, chain_tip)
|
) -> impl Iterator<Item = (S, FullTxOut<ObservedAs<A>>)> + 'a {
|
||||||
.map(|r| r.expect("error in infallible"))
|
self.try_filter_chain_txouts(chain, chain_tip, outpoints)
|
||||||
|
.map(|r| r.expect("oracle is infallible"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List unspent outputs (UTXOs) that are in `chain` with `chain_tip`.
|
/// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
|
||||||
|
/// `chain` with `chain_tip`.
|
||||||
///
|
///
|
||||||
/// Floating outputs are not iterated over.
|
/// `outpoints` is a list of outpoints we are interested in, coupled with the associated txout's
|
||||||
|
/// script pubkey index (`S`).
|
||||||
|
///
|
||||||
|
/// Floating outputs are ignored.
|
||||||
///
|
///
|
||||||
/// # Error
|
/// # Error
|
||||||
///
|
///
|
||||||
/// An item can be an error if the [`ChainOracle`] implementation fails. If the oracle is
|
/// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
|
||||||
/// infallible, [`list_chain_unspents`] can be used instead.
|
/// fails.
|
||||||
///
|
///
|
||||||
/// [`list_chain_unspents`]: Self::list_chain_unspents
|
/// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used
|
||||||
pub fn try_list_chain_unspents<'a, C: ChainOracle + 'a>(
|
/// instead.
|
||||||
|
///
|
||||||
|
/// [`filter_chain_unspents`]: Self::filter_chain_unspents
|
||||||
|
pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, S: Clone + 'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
chain_tip: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
|
outpoints: impl IntoIterator<Item = (S, OutPoint)> + 'a,
|
||||||
self.try_list_chain_txouts(chain, chain_tip)
|
) -> impl Iterator<Item = Result<(S, FullTxOut<ObservedAs<A>>), C::Error>> + 'a {
|
||||||
.filter(|r| matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
self.try_filter_chain_txouts(chain, chain_tip, outpoints)
|
||||||
|
.filter(|r| !matches!(r, Ok((_, full_txo)) if full_txo.spent_by.is_some()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List unspent outputs (UTXOs) that are in `chain` with `chain_tip`.
|
/// 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_list_chain_unspents`].
|
/// This is the infallible version of [`try_filter_chain_unspents`].
|
||||||
///
|
///
|
||||||
/// [`try_list_chain_unspents`]: Self::try_list_chain_unspents
|
/// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents
|
||||||
pub fn list_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a>(
|
pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, S: Clone + 'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
|
txouts: impl IntoIterator<Item = (S, OutPoint)> + 'a,
|
||||||
self.try_list_chain_unspents(chain, static_block)
|
) -> impl Iterator<Item = (S, FullTxOut<ObservedAs<A>>)> + 'a {
|
||||||
.map(|r| r.expect("error is infallible"))
|
self.try_filter_chain_unspents(chain, chain_tip, txouts)
|
||||||
|
.map(|r| r.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.
|
||||||
|
///
|
||||||
|
/// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be
|
||||||
|
/// used instead.
|
||||||
|
///
|
||||||
|
/// [`balance`]: Self::balance
|
||||||
|
pub fn try_balance<C: ChainOracle, S: Clone>(
|
||||||
|
&self,
|
||||||
|
chain: &C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
outpoints: impl IntoIterator<Item = (S, OutPoint)>,
|
||||||
|
mut trust_predicate: impl FnMut(&S, &Script) -> bool,
|
||||||
|
) -> Result<Balance, C::Error> {
|
||||||
|
let mut immature = 0;
|
||||||
|
let mut trusted_pending = 0;
|
||||||
|
let mut untrusted_pending = 0;
|
||||||
|
let mut confirmed = 0;
|
||||||
|
|
||||||
|
for res in self.try_filter_chain_unspents(chain, chain_tip, outpoints) {
|
||||||
|
let (spk_i, txout) = res?;
|
||||||
|
|
||||||
|
match &txout.chain_position {
|
||||||
|
ObservedAs::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ObservedAs::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`].
|
||||||
|
///
|
||||||
|
/// [`try_balance`]: Self::try_balance
|
||||||
|
pub fn balance<C: ChainOracle<Error = Infallible>, S: Clone>(
|
||||||
|
&self,
|
||||||
|
chain: &C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
outpoints: impl IntoIterator<Item = (S, OutPoint)>,
|
||||||
|
trust_predicate: impl FnMut(&S, &Script) -> bool,
|
||||||
|
) -> Balance {
|
||||||
|
self.try_balance(chain, chain_tip, outpoints, trust_predicate)
|
||||||
|
.expect("oracle is infallible")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,23 +236,36 @@ fn test_list_owned_txouts() {
|
|||||||
.map(|&hash| BlockId { height, hash })
|
.map(|&hash| BlockId { height, hash })
|
||||||
.expect("block must exist");
|
.expect("block must exist");
|
||||||
let txouts = graph
|
let txouts = graph
|
||||||
.list_owned_txouts(&local_chain, chain_tip)
|
.graph()
|
||||||
|
.filter_chain_txouts(
|
||||||
|
&local_chain,
|
||||||
|
chain_tip,
|
||||||
|
graph.index.outpoints().iter().cloned(),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let utxos = graph
|
let utxos = graph
|
||||||
.list_owned_unspents(&local_chain, chain_tip)
|
.graph()
|
||||||
|
.filter_chain_unspents(
|
||||||
|
&local_chain,
|
||||||
|
chain_tip,
|
||||||
|
graph.index.outpoints().iter().cloned(),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let balance = graph.balance(&local_chain, chain_tip, |spk: &Script| {
|
let balance = graph.graph().balance(
|
||||||
trusted_spks.contains(spk)
|
&local_chain,
|
||||||
});
|
chain_tip,
|
||||||
|
graph.index.outpoints().iter().cloned(),
|
||||||
|
|_, spk: &Script| trusted_spks.contains(spk),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(txouts.len(), 5);
|
assert_eq!(txouts.len(), 5);
|
||||||
assert_eq!(utxos.len(), 4);
|
assert_eq!(utxos.len(), 4);
|
||||||
|
|
||||||
let confirmed_txouts_txid = txouts
|
let confirmed_txouts_txid = txouts
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|full_txout| {
|
.filter_map(|(_, full_txout)| {
|
||||||
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
|
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
|
||||||
Some(full_txout.outpoint.txid)
|
Some(full_txout.outpoint.txid)
|
||||||
} else {
|
} else {
|
||||||
@ -263,7 +276,7 @@ fn test_list_owned_txouts() {
|
|||||||
|
|
||||||
let unconfirmed_txouts_txid = txouts
|
let unconfirmed_txouts_txid = txouts
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|full_txout| {
|
.filter_map(|(_, full_txout)| {
|
||||||
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
|
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
|
||||||
Some(full_txout.outpoint.txid)
|
Some(full_txout.outpoint.txid)
|
||||||
} else {
|
} else {
|
||||||
@ -274,7 +287,7 @@ fn test_list_owned_txouts() {
|
|||||||
|
|
||||||
let confirmed_utxos_txid = utxos
|
let confirmed_utxos_txid = utxos
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|full_txout| {
|
.filter_map(|(_, full_txout)| {
|
||||||
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
|
if matches!(full_txout.chain_position, ObservedAs::Confirmed(_)) {
|
||||||
Some(full_txout.outpoint.txid)
|
Some(full_txout.outpoint.txid)
|
||||||
} else {
|
} else {
|
||||||
@ -285,7 +298,7 @@ fn test_list_owned_txouts() {
|
|||||||
|
|
||||||
let unconfirmed_utxos_txid = utxos
|
let unconfirmed_utxos_txid = utxos
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|full_txout| {
|
.filter_map(|(_, full_txout)| {
|
||||||
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
|
if matches!(full_txout.chain_position, ObservedAs::Unconfirmed(_)) {
|
||||||
Some(full_txout.outpoint.txid)
|
Some(full_txout.outpoint.txid)
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user