[bdk_chain_redesign] Fix Anchor
definition + docs
Previously, I have misunderstood the definition of anchor. If a tx is anchored in a block, it does not necessarily mean it is confirmed in that block. The tx can be confirmed in an ancestor block of the anchor block. With this new definition, we need a new trait `ConfirmationHeight` that has one method `confirmation_height`. This trait can be used to extend `Anchor` for those implementations that can give us the exact conirmation height of a tx (which is useful in most cases). Another change is to add another variant to the `ObservedAs` enum; `ObservedAs::ConfirmedImplicit(A)`. If a tx does not have an anchor, but another tx that spends it has an anchor that in in the best chain, we can assume that tx is also in the best chain. The logic of `TxGraph::try_get_chain_position` is also changed to reflect this. Some methods from `IndexedTxGraph` have been moved to `TxGraph` as they do not require the `Indexer`. Some `TxGraph` methods have been renamed for clarity and consistency. Also more docs are added.
This commit is contained in:
parent
001efdd1cb
commit
81436fcd72
@ -2,16 +2,19 @@ use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
sparse_chain::{self, ChainPosition},
|
sparse_chain::{self, ChainPosition},
|
||||||
BlockAnchor, COINBASE_MATURITY,
|
Anchor, ConfirmationHeight, COINBASE_MATURITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents an observation of some chain data.
|
/// Represents an observation of some chain data.
|
||||||
///
|
///
|
||||||
/// The generic `A` should be a [`BlockAnchor`] implementation.
|
/// The generic `A` should be a [`Anchor`] implementation.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
|
||||||
pub enum ObservedAs<A> {
|
pub enum ObservedAs<A> {
|
||||||
/// The chain data is seen as confirmed, and in anchored by `A`.
|
/// The chain data is seen as confirmed, and in anchored by `A`.
|
||||||
Confirmed(A),
|
Confirmed(A),
|
||||||
|
/// The chain data is assumed to be confirmed, because a transaction that spends it is anchored
|
||||||
|
/// by `A`.
|
||||||
|
ConfirmedImplicit(A),
|
||||||
/// The chain data is seen in mempool at this given timestamp.
|
/// The chain data is seen in mempool at this given timestamp.
|
||||||
Unconfirmed(u64),
|
Unconfirmed(u64),
|
||||||
}
|
}
|
||||||
@ -20,6 +23,7 @@ impl<A: Clone> ObservedAs<&A> {
|
|||||||
pub fn cloned(self) -> ObservedAs<A> {
|
pub fn cloned(self) -> ObservedAs<A> {
|
||||||
match self {
|
match self {
|
||||||
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
|
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
|
||||||
|
ObservedAs::ConfirmedImplicit(a) => ObservedAs::ConfirmedImplicit(a.clone()),
|
||||||
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
|
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,7 +164,7 @@ impl Default for BlockId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockAnchor for BlockId {
|
impl Anchor for BlockId {
|
||||||
fn anchor_block(&self) -> BlockId {
|
fn anchor_block(&self) -> BlockId {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
@ -241,20 +245,23 @@ impl<P: ChainPosition> FullTxOut<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
|
impl<A: Anchor + ConfirmationHeight> FullTxOut<ObservedAs<A>> {
|
||||||
/// Whether the `txout` is considered mature.
|
/// Whether the `txout` is considered mature.
|
||||||
///
|
///
|
||||||
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
|
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
|
||||||
/// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
|
/// [`ObservedAs<A>`] where `A` implements [`Anchor`].
|
||||||
///
|
///
|
||||||
/// [`is_mature`]: Self::is_mature
|
/// [`is_mature`]: Self::is_mature
|
||||||
pub fn is_observed_as_confirmed_and_mature(&self, tip: u32) -> bool {
|
pub fn is_observed_as_mature(&self, tip: u32) -> bool {
|
||||||
if !self.is_on_coinbase {
|
if !self.is_on_coinbase {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx_height = match &self.chain_position {
|
let tx_height = match &self.chain_position {
|
||||||
ObservedAs::Confirmed(anchor) => anchor.anchor_block().height,
|
ObservedAs::Confirmed(anchor) => anchor.confirmation_height(),
|
||||||
|
// although we do not know the exact confirm height, the returned height here is the
|
||||||
|
// "upper bound" so only false-negatives are possible
|
||||||
|
ObservedAs::ConfirmedImplicit(anchor) => anchor.confirmation_height(),
|
||||||
ObservedAs::Unconfirmed(_) => {
|
ObservedAs::Unconfirmed(_) => {
|
||||||
debug_assert!(false, "coinbase tx can never be unconfirmed");
|
debug_assert!(false, "coinbase tx can never be unconfirmed");
|
||||||
return false;
|
return false;
|
||||||
@ -274,22 +281,24 @@ impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
|
|||||||
/// Currently this method does not take into account the locktime.
|
/// Currently this method does not take into account the locktime.
|
||||||
///
|
///
|
||||||
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
|
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
|
||||||
/// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
|
/// being a [`ObservedAs<A>`] where `A` implements [`Anchor`].
|
||||||
///
|
///
|
||||||
/// [`is_spendable_at`]: Self::is_spendable_at
|
/// [`is_spendable_at`]: Self::is_spendable_at
|
||||||
pub fn is_observed_as_confirmed_and_spendable(&self, tip: u32) -> bool {
|
pub fn is_observed_as_confirmed_and_spendable(&self, tip: u32) -> bool {
|
||||||
if !self.is_observed_as_confirmed_and_mature(tip) {
|
if !self.is_observed_as_mature(tip) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.chain_position {
|
let confirmation_height = match &self.chain_position {
|
||||||
ObservedAs::Confirmed(anchor) => {
|
ObservedAs::Confirmed(anchor) => anchor.confirmation_height(),
|
||||||
if anchor.anchor_block().height > tip {
|
// although we do not know the exact confirm height, the returned height here is the
|
||||||
return false;
|
// "upper bound" so only false-negatives are possible
|
||||||
}
|
ObservedAs::ConfirmedImplicit(anchor) => anchor.confirmation_height(),
|
||||||
}
|
|
||||||
ObservedAs::Unconfirmed(_) => return false,
|
ObservedAs::Unconfirmed(_) => return false,
|
||||||
};
|
};
|
||||||
|
if confirmation_height > tip {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// if the spending tx is confirmed within tip height, the txout is no longer spendable
|
// if the spending tx is confirmed within tip height, the txout is no longer spendable
|
||||||
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
|
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
|
|
||||||
use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
keychain::Balance,
|
keychain::Balance,
|
||||||
tx_graph::{Additions, TxGraph, TxNode},
|
tx_graph::{Additions, CanonicalTx, TxGraph},
|
||||||
Append, BlockAnchor, BlockId, ChainOracle, FullTxOut, ObservedAs,
|
Anchor, Append, BlockId, ChainOracle, ConfirmationHeight, FullTxOut, ObservedAs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
|
||||||
|
///
|
||||||
|
/// This structure ensures that [`TxGraph`] and [`Indexer`] are updated atomically.
|
||||||
pub struct IndexedTxGraph<A, I> {
|
pub struct IndexedTxGraph<A, I> {
|
||||||
/// Transaction index.
|
/// Transaction index.
|
||||||
pub index: I,
|
pub index: I,
|
||||||
@ -23,7 +26,7 @@ impl<A, I: Default> Default for IndexedTxGraph<A, I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
|
||||||
/// 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
|
||||||
@ -47,195 +50,191 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
|
|
||||||
self.graph.apply_additions(graph_additions);
|
self.graph.apply_additions(graph_additions);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert a `txout` that exists in `outpoint` with the given `observation`.
|
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
|
||||||
|
where
|
||||||
|
I::Additions: Default + Append,
|
||||||
|
{
|
||||||
|
/// Apply an `update` directly.
|
||||||
|
///
|
||||||
|
/// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`IndexedAdditions`].
|
||||||
|
pub fn apply_update(&mut self, update: TxGraph<A>) -> IndexedAdditions<A, I::Additions> {
|
||||||
|
let graph_additions = self.graph.apply_update(update);
|
||||||
|
|
||||||
|
let mut index_additions = I::Additions::default();
|
||||||
|
for added_tx in &graph_additions.tx {
|
||||||
|
index_additions.append(self.index.index_tx(added_tx));
|
||||||
|
}
|
||||||
|
for (&added_outpoint, added_txout) in &graph_additions.txout {
|
||||||
|
index_additions.append(self.index.index_txout(added_outpoint, added_txout));
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedAdditions {
|
||||||
|
graph_additions,
|
||||||
|
index_additions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a floating `txout` of given `outpoint`.
|
||||||
pub fn insert_txout(
|
pub fn insert_txout(
|
||||||
&mut self,
|
&mut self,
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
txout: &TxOut,
|
txout: &TxOut,
|
||||||
observation: ObservedAs<A>,
|
|
||||||
) -> IndexedAdditions<A, I::Additions> {
|
) -> IndexedAdditions<A, I::Additions> {
|
||||||
IndexedAdditions {
|
let mut update = TxGraph::<A>::default();
|
||||||
graph_additions: {
|
let _ = update.insert_txout(outpoint, txout.clone());
|
||||||
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
|
self.apply_update(update)
|
||||||
graph_additions.append(match observation {
|
|
||||||
ObservedAs::Confirmed(anchor) => {
|
|
||||||
self.graph.insert_anchor(outpoint.txid, anchor)
|
|
||||||
}
|
|
||||||
ObservedAs::Unconfirmed(seen_at) => {
|
|
||||||
self.graph.insert_seen_at(outpoint.txid, seen_at)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
graph_additions
|
|
||||||
},
|
|
||||||
index_additions: <I as TxIndex>::index_txout(&mut self.index, outpoint, txout),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert and index a transaction into the graph.
|
||||||
|
///
|
||||||
|
/// `anchors` can be provided to anchor the transaction to various blocks. `seen_at` is a
|
||||||
|
/// unix timestamp of when the transaction is last seen.
|
||||||
pub fn insert_tx(
|
pub fn insert_tx(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
observation: ObservedAs<A>,
|
anchors: impl IntoIterator<Item = A>,
|
||||||
|
seen_at: Option<u64>,
|
||||||
) -> IndexedAdditions<A, I::Additions> {
|
) -> IndexedAdditions<A, I::Additions> {
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
|
|
||||||
IndexedAdditions {
|
let mut update = TxGraph::<A>::default();
|
||||||
graph_additions: {
|
if self.graph.get_tx(txid).is_none() {
|
||||||
let mut graph_additions = self.graph.insert_tx(tx.clone());
|
let _ = update.insert_tx(tx.clone());
|
||||||
graph_additions.append(match observation {
|
|
||||||
ObservedAs::Confirmed(anchor) => self.graph.insert_anchor(txid, anchor),
|
|
||||||
ObservedAs::Unconfirmed(seen_at) => self.graph.insert_seen_at(txid, seen_at),
|
|
||||||
});
|
|
||||||
graph_additions
|
|
||||||
},
|
|
||||||
index_additions: <I as TxIndex>::index_tx(&mut self.index, tx),
|
|
||||||
}
|
}
|
||||||
|
for anchor in anchors.into_iter() {
|
||||||
|
let _ = update.insert_anchor(txid, anchor);
|
||||||
|
}
|
||||||
|
if let Some(seen_at) = seen_at {
|
||||||
|
let _ = update.insert_seen_at(txid, seen_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.apply_update(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert relevant transactions from the given `txs` iterator.
|
||||||
|
///
|
||||||
|
/// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant
|
||||||
|
/// transactions in `txs` will be ignored.
|
||||||
|
///
|
||||||
|
/// `anchors` can be provided to anchor the transactions to blocks. `seen_at` is a unix
|
||||||
|
/// timestamp of when the transactions are last seen.
|
||||||
pub fn insert_relevant_txs<'t, T>(
|
pub fn insert_relevant_txs<'t, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
txs: T,
|
txs: T,
|
||||||
observation: ObservedAs<A>,
|
anchors: impl IntoIterator<Item = A> + Clone,
|
||||||
|
seen_at: Option<u64>,
|
||||||
) -> IndexedAdditions<A, I::Additions>
|
) -> IndexedAdditions<A, I::Additions>
|
||||||
where
|
where
|
||||||
T: Iterator<Item = &'t Transaction>,
|
T: Iterator<Item = &'t Transaction>,
|
||||||
I::Additions: Default + Append,
|
|
||||||
{
|
{
|
||||||
txs.filter_map(|tx| {
|
txs.filter_map(|tx| match self.index.is_tx_relevant(tx) {
|
||||||
if self.index.is_tx_relevant(tx) {
|
true => Some(self.insert_tx(tx, anchors.clone(), seen_at)),
|
||||||
Some(self.insert_tx(tx, observation.clone()))
|
false => None,
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.fold(IndexedAdditions::default(), |mut acc, other| {
|
.fold(Default::default(), |mut acc, other| {
|
||||||
acc.append(other);
|
acc.append(other);
|
||||||
acc
|
acc
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// [TODO] Have to methods, one for relevant-only, and one for any. Have one in `TxGraph`.
|
impl<A: Anchor, I: OwnedIndexer> IndexedTxGraph<A, I> {
|
||||||
pub fn try_list_chain_txs<'a, C>(
|
pub fn try_list_owned_txs<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
|
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
|
||||||
where
|
where
|
||||||
C: ChainOracle + 'a,
|
C: ChainOracle + 'a,
|
||||||
{
|
{
|
||||||
self.graph
|
self.graph
|
||||||
.full_transactions()
|
.full_txs()
|
||||||
.filter(|tx| self.index.is_tx_relevant(tx))
|
.filter(|node| tx_alters_owned_utxo_set(&self.graph, &self.index, node.txid, node.tx))
|
||||||
.filter_map(move |tx| {
|
.filter_map(move |tx_node| {
|
||||||
self.graph
|
self.graph
|
||||||
.try_get_chain_position(chain, static_block, tx.txid)
|
.try_get_chain_position(chain, chain_tip, tx_node.txid)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
v.map(|observed_in| CanonicalTx {
|
v.map(|observed_as| CanonicalTx {
|
||||||
observed_as: observed_in,
|
observed_as,
|
||||||
tx,
|
node: tx_node,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_chain_txs<'a, C>(
|
pub fn list_owned_txs<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
|
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible> + 'a,
|
C: ChainOracle<Error = Infallible> + 'a,
|
||||||
{
|
{
|
||||||
self.try_list_chain_txs(chain, static_block)
|
self.try_list_owned_txs(chain, chain_tip)
|
||||||
.map(|r| r.expect("error is infallible"))
|
.map(|r| r.expect("chain oracle is infallible"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_list_chain_txouts<'a, C>(
|
pub fn try_list_owned_txouts<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle + 'a,
|
C: ChainOracle + 'a,
|
||||||
{
|
{
|
||||||
self.graph
|
self.graph()
|
||||||
.all_txouts()
|
.try_list_chain_txouts(chain, chain_tip, |_, txout| {
|
||||||
.filter(|&(op, txo)| self.index.is_txout_relevant(op, txo))
|
self.index.is_spk_owned(&txout.script_pubkey)
|
||||||
.filter_map(move |(op, txout)| -> Option<Result<_, C::Error>> {
|
|
||||||
let graph_tx = self.graph.get_tx(op.txid)?;
|
|
||||||
|
|
||||||
let is_on_coinbase = graph_tx.is_coin_base();
|
|
||||||
|
|
||||||
let chain_position =
|
|
||||||
match self
|
|
||||||
.graph
|
|
||||||
.try_get_chain_position(chain, static_block, op.txid)
|
|
||||||
{
|
|
||||||
Ok(Some(observed_at)) => observed_at.cloned(),
|
|
||||||
Ok(None) => return None,
|
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let spent_by = match self.graph.try_get_spend_in_chain(chain, static_block, op) {
|
|
||||||
Ok(Some((obs, txid))) => Some((obs.cloned(), txid)),
|
|
||||||
Ok(None) => None,
|
|
||||||
Err(err) => return Some(Err(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let full_txout = FullTxOut {
|
|
||||||
outpoint: op,
|
|
||||||
txout: txout.clone(),
|
|
||||||
chain_position,
|
|
||||||
spent_by,
|
|
||||||
is_on_coinbase,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Ok(full_txout))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_chain_txouts<'a, C>(
|
pub fn list_owned_txouts<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible> + 'a,
|
C: ChainOracle + 'a,
|
||||||
{
|
{
|
||||||
self.try_list_chain_txouts(chain, static_block)
|
self.try_list_owned_txouts(chain, chain_tip)
|
||||||
.map(|r| r.expect("error in infallible"))
|
.map(|r| r.expect("oracle is infallible"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return relevant unspents.
|
pub fn try_list_owned_unspents<'a, C>(
|
||||||
pub fn try_list_chain_utxos<'a, C>(
|
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle + 'a,
|
C: ChainOracle + 'a,
|
||||||
{
|
{
|
||||||
self.try_list_chain_txouts(chain, static_block)
|
self.graph()
|
||||||
.filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
.try_list_chain_unspents(chain, chain_tip, |_, txout| {
|
||||||
|
self.index.is_spk_owned(&txout.script_pubkey)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_chain_utxos<'a, C>(
|
pub fn list_owned_unspents<'a, C>(
|
||||||
&'a self,
|
&'a self,
|
||||||
chain: &'a C,
|
chain: &'a C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible> + 'a,
|
C: ChainOracle + 'a,
|
||||||
{
|
{
|
||||||
self.try_list_chain_utxos(chain, static_block)
|
self.try_list_owned_unspents(chain, chain_tip)
|
||||||
.map(|r| r.expect("error is infallible"))
|
.map(|r| r.expect("oracle is infallible"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Anchor + ConfirmationHeight, I: OwnedIndexer> IndexedTxGraph<A, I> {
|
||||||
pub fn try_balance<C, F>(
|
pub fn try_balance<C, F>(
|
||||||
&self,
|
&self,
|
||||||
chain: &C,
|
chain: &C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
tip: u32,
|
tip: u32,
|
||||||
mut should_trust: F,
|
mut should_trust: F,
|
||||||
) -> Result<Balance, C::Error>
|
) -> Result<Balance, C::Error>
|
||||||
@ -248,13 +247,13 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
let mut untrusted_pending = 0;
|
let mut untrusted_pending = 0;
|
||||||
let mut confirmed = 0;
|
let mut confirmed = 0;
|
||||||
|
|
||||||
for res in self.try_list_chain_txouts(chain, static_block) {
|
for res in self.try_list_owned_txouts(chain, chain_tip) {
|
||||||
let txout = res?;
|
let txout = res?;
|
||||||
|
|
||||||
match &txout.chain_position {
|
match &txout.chain_position {
|
||||||
ObservedAs::Confirmed(_) => {
|
ObservedAs::Confirmed(_) | ObservedAs::ConfirmedImplicit(_) => {
|
||||||
if txout.is_on_coinbase {
|
if txout.is_on_coinbase {
|
||||||
if txout.is_observed_as_confirmed_and_mature(tip) {
|
if txout.is_observed_as_mature(tip) {
|
||||||
confirmed += txout.txout.value;
|
confirmed += txout.txout.value;
|
||||||
} else {
|
} else {
|
||||||
immature += txout.txout.value;
|
immature += txout.txout.value;
|
||||||
@ -304,7 +303,10 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
{
|
{
|
||||||
let mut sum = 0;
|
let mut sum = 0;
|
||||||
for txo_res in self.try_list_chain_txouts(chain, static_block) {
|
for txo_res in self
|
||||||
|
.graph()
|
||||||
|
.try_list_chain_txouts(chain, static_block, |_, _| true)
|
||||||
|
{
|
||||||
let txo = txo_res?;
|
let txo = txo_res?;
|
||||||
if txo.is_observed_as_confirmed_and_spendable(height) {
|
if txo.is_observed_as_confirmed_and_spendable(height) {
|
||||||
sum += txo.txout.value;
|
sum += txo.txout.value;
|
||||||
@ -339,7 +341,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub struct IndexedAdditions<A, IA> {
|
pub struct IndexedAdditions<A, IA> {
|
||||||
/// [`TxGraph`] additions.
|
/// [`TxGraph`] additions.
|
||||||
pub graph_additions: Additions<A>,
|
pub graph_additions: Additions<A>,
|
||||||
/// [`TxIndex`] additions.
|
/// [`Indexer`] additions.
|
||||||
pub index_additions: IA,
|
pub index_additions: IA,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,24 +354,15 @@ impl<A, IA: Default> Default for IndexedAdditions<A, IA> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: BlockAnchor, IA: Append> Append for IndexedAdditions<A, IA> {
|
impl<A: Anchor, IA: Append> Append for IndexedAdditions<A, IA> {
|
||||||
fn append(&mut self, other: Self) {
|
fn append(&mut self, other: Self) {
|
||||||
self.graph_additions.append(other.graph_additions);
|
self.graph_additions.append(other.graph_additions);
|
||||||
self.index_additions.append(other.index_additions);
|
self.index_additions.append(other.index_additions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
|
/// Represents a structure that can index transaction data.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
pub trait Indexer {
|
||||||
pub struct CanonicalTx<'a, T, A> {
|
|
||||||
/// Where the transaction is observed (in a block or in mempool).
|
|
||||||
pub observed_as: ObservedAs<&'a A>,
|
|
||||||
/// The transaction with anchors and last seen timestamp.
|
|
||||||
pub tx: TxNode<'a, T, A>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents an index of transaction data.
|
|
||||||
pub trait TxIndex {
|
|
||||||
/// The resultant "additions" when new transaction data is indexed.
|
/// The resultant "additions" when new transaction data is indexed.
|
||||||
type Additions;
|
type Additions;
|
||||||
|
|
||||||
@ -382,11 +375,30 @@ pub trait TxIndex {
|
|||||||
/// Apply additions to itself.
|
/// Apply additions to itself.
|
||||||
fn apply_additions(&mut self, additions: Self::Additions);
|
fn apply_additions(&mut self, additions: Self::Additions);
|
||||||
|
|
||||||
/// Returns whether the txout is marked as relevant in the index.
|
/// Determines whether the transaction should be included in the index.
|
||||||
fn is_txout_relevant(&self, outpoint: OutPoint, txout: &TxOut) -> bool;
|
|
||||||
|
|
||||||
/// Returns whether the transaction is marked as relevant in the index.
|
|
||||||
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SpkIndex: TxIndex {}
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tx_alters_owned_utxo_set<A, I>(
|
||||||
|
graph: &TxGraph<A>,
|
||||||
|
index: &I,
|
||||||
|
txid: Txid,
|
||||||
|
tx: &Transaction,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
A: Anchor,
|
||||||
|
I: OwnedIndexer,
|
||||||
|
{
|
||||||
|
let prev_spends = (0..tx.input.len() as u32)
|
||||||
|
.map(|vout| OutPoint { txid, vout })
|
||||||
|
.filter_map(|op| graph.get_txout(op));
|
||||||
|
prev_spends
|
||||||
|
.chain(&tx.output)
|
||||||
|
.any(|txout| index.is_spk_owned(&txout.script_pubkey))
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
collections::*,
|
collections::*,
|
||||||
indexed_tx_graph::TxIndex,
|
indexed_tx_graph::Indexer,
|
||||||
miniscript::{Descriptor, DescriptorPublicKey},
|
miniscript::{Descriptor, DescriptorPublicKey},
|
||||||
ForEachTxOut, SpkTxOutIndex,
|
ForEachTxOut, SpkTxOutIndex,
|
||||||
};
|
};
|
||||||
@ -91,7 +91,7 @@ impl<K> Deref for KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug + 'static> Indexer for KeychainTxOutIndex<K> {
|
||||||
type Additions = DerivationAdditions<K>;
|
type Additions = DerivationAdditions<K>;
|
||||||
|
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
||||||
@ -106,10 +106,6 @@ impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> {
|
|||||||
self.apply_additions(additions)
|
self.apply_additions(additions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_txout_relevant(&self, _outpoint: OutPoint, txout: &TxOut) -> bool {
|
|
||||||
self.index_of_spk(&txout.script_pubkey).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
|
fn is_tx_relevant(&self, tx: &bitcoin::Transaction) -> bool {
|
||||||
self.is_relevant(tx)
|
self.is_relevant(tx)
|
||||||
}
|
}
|
||||||
|
@ -997,7 +997,7 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
/// `chain` for this to return `Some`.
|
/// `chain` for this to return `Some`.
|
||||||
pub fn spent_by<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<(&P, Txid)> {
|
pub fn spent_by<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<(&P, Txid)> {
|
||||||
graph
|
graph
|
||||||
.outspends(outpoint)
|
.output_spends(outpoint)
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|&txid| Some((self.tx_position(txid)?, txid)))
|
.find_map(|&txid| Some((self.tx_position(txid)?, txid)))
|
||||||
}
|
}
|
||||||
|
@ -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::TxIndex,
|
indexed_tx_graph::Indexer,
|
||||||
ForEachTxOut,
|
ForEachTxOut,
|
||||||
};
|
};
|
||||||
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
|
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
|
||||||
@ -53,7 +53,7 @@ impl<I> Default for SpkTxOutIndex<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> {
|
impl<I: Clone + Ord + 'static> Indexer for SpkTxOutIndex<I> {
|
||||||
type Additions = ();
|
type Additions = ();
|
||||||
|
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
||||||
@ -70,10 +70,6 @@ impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> {
|
|||||||
// This applies nothing.
|
// This applies nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_txout_relevant(&self, _outpoint: OutPoint, txout: &TxOut) -> bool {
|
|
||||||
self.index_of_spk(&txout.script_pubkey).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_tx_relevant(&self, tx: &Transaction) -> bool {
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool {
|
||||||
self.is_relevant(tx)
|
self.is_relevant(tx)
|
||||||
}
|
}
|
||||||
|
@ -40,25 +40,34 @@ impl ForEachTxOut for Transaction {
|
|||||||
/// assume that transaction A is also confirmed in the best chain. This does not necessarily mean
|
/// assume that transaction A is also confirmed in the best chain. This does not necessarily mean
|
||||||
/// that transaction A is confirmed in block B. It could also mean transaction A is confirmed in a
|
/// that transaction A is confirmed in block B. It could also mean transaction A is confirmed in a
|
||||||
/// parent block of B.
|
/// parent block of B.
|
||||||
pub trait BlockAnchor:
|
pub trait Anchor:
|
||||||
core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash + Send + Sync + 'static
|
core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
/// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.
|
/// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.
|
||||||
fn anchor_block(&self) -> BlockId;
|
fn anchor_block(&self) -> BlockId;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: BlockAnchor> BlockAnchor for &'static A {
|
impl<A: Anchor> Anchor for &'static A {
|
||||||
fn anchor_block(&self) -> BlockId {
|
fn anchor_block(&self) -> BlockId {
|
||||||
<A as BlockAnchor>::anchor_block(self)
|
<A as Anchor>::anchor_block(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockAnchor for (u32, BlockHash) {
|
impl Anchor for (u32, BlockHash) {
|
||||||
fn anchor_block(&self) -> BlockId {
|
fn anchor_block(&self) -> BlockId {
|
||||||
(*self).into()
|
(*self).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait that returns a confirmation height.
|
||||||
|
///
|
||||||
|
/// This is typically used to provide an [`Anchor`] implementation the exact confirmation height of
|
||||||
|
/// the data being anchored.
|
||||||
|
pub trait ConfirmationHeight {
|
||||||
|
/// Returns the confirmation height.
|
||||||
|
fn confirmation_height(&self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait that makes an object appendable.
|
/// Trait that makes an object appendable.
|
||||||
pub trait Append {
|
pub trait Append {
|
||||||
/// Append another object of the same type onto `self`.
|
/// Append another object of the same type onto `self`.
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
//! assert!(additions.is_empty());
|
//! assert!(additions.is_empty());
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{collections::*, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, ObservedAs};
|
use crate::{collections::*, Anchor, BlockId, ChainOracle, ForEachTxOut, FullTxOut, ObservedAs};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||||
use core::{
|
use core::{
|
||||||
@ -91,7 +91,7 @@ impl<A> Default for TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An outward-facing representation of a (transaction) node in the [`TxGraph`].
|
/// An outward-facing view of a (transaction) node in the [`TxGraph`].
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct TxNode<'a, T, A> {
|
pub struct TxNode<'a, T, A> {
|
||||||
/// Txid of the transaction.
|
/// Txid of the transaction.
|
||||||
@ -139,8 +139,19 @@ impl Default for TxNodeInternal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct CanonicalTx<'a, T, A> {
|
||||||
|
/// How the transaction is observed as (confirmed or unconfirmed).
|
||||||
|
pub observed_as: ObservedAs<&'a A>,
|
||||||
|
/// The transaction node (as part of the graph).
|
||||||
|
pub node: TxNode<'a, T, A>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<A> TxGraph<A> {
|
impl<A> TxGraph<A> {
|
||||||
/// Iterate over all tx outputs known by [`TxGraph`].
|
/// Iterate over all tx outputs known by [`TxGraph`].
|
||||||
|
///
|
||||||
|
/// This includes txouts of both full transactions as well as floating transactions.
|
||||||
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
||||||
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
|
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
|
||||||
TxNodeInternal::Whole(tx) => tx
|
TxNodeInternal::Whole(tx) => tx
|
||||||
@ -156,8 +167,26 @@ impl<A> TxGraph<A> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over floating txouts known by [`TxGraph`].
|
||||||
|
///
|
||||||
|
/// Floating txouts are txouts that do not have the residing full transaction contained in the
|
||||||
|
/// graph.
|
||||||
|
pub fn floating_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
||||||
|
self.txs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(txid, (tx_node, _, _))| match tx_node {
|
||||||
|
TxNodeInternal::Whole(_) => None,
|
||||||
|
TxNodeInternal::Partial(txouts) => Some(
|
||||||
|
txouts
|
||||||
|
.iter()
|
||||||
|
.map(|(&vout, txout)| (OutPoint::new(*txid, vout), txout)),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over all full transactions in the graph.
|
/// Iterate over all full transactions in the graph.
|
||||||
pub fn full_transactions(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
|
pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
|
||||||
self.txs
|
self.txs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
|
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
|
||||||
@ -201,8 +230,10 @@ impl<A> TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns known outputs of a given `txid`.
|
||||||
|
///
|
||||||
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
|
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
|
||||||
pub fn txouts(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
|
pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
|
||||||
Some(match &self.txs.get(&txid)?.0 {
|
Some(match &self.txs.get(&txid)?.0 {
|
||||||
TxNodeInternal::Whole(tx) => tx
|
TxNodeInternal::Whole(tx) => tx
|
||||||
.output
|
.output
|
||||||
@ -251,7 +282,7 @@ impl<A> TxGraph<A> {
|
|||||||
///
|
///
|
||||||
/// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
|
/// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
|
||||||
/// the returned set will never be in the same active-chain.
|
/// the returned set will never be in the same active-chain.
|
||||||
pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
|
pub fn output_spends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
|
||||||
self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
|
self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +292,7 @@ impl<A> TxGraph<A> {
|
|||||||
///
|
///
|
||||||
/// - `vout` is the provided `txid`'s outpoint that is being spent
|
/// - `vout` is the provided `txid`'s outpoint that is being spent
|
||||||
/// - `txid-set` is the set of txids spending the `vout`.
|
/// - `txid-set` is the set of txids spending the `vout`.
|
||||||
pub fn tx_outspends(
|
pub fn tx_spends(
|
||||||
&self,
|
&self,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
|
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
|
||||||
@ -275,23 +306,6 @@ impl<A> TxGraph<A> {
|
|||||||
.map(|(outpoint, spends)| (outpoint.vout, spends))
|
.map(|(outpoint, spends)| (outpoint.vout, spends))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all partial transactions (outputs only) in the graph.
|
|
||||||
pub fn partial_transactions(
|
|
||||||
&self,
|
|
||||||
) -> impl Iterator<Item = TxNode<'_, BTreeMap<u32, TxOut>, A>> {
|
|
||||||
self.txs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
|
|
||||||
TxNodeInternal::Whole(_) => None,
|
|
||||||
TxNodeInternal::Partial(partial) => Some(TxNode {
|
|
||||||
txid,
|
|
||||||
tx: partial,
|
|
||||||
anchors,
|
|
||||||
last_seen_unconfirmed: *last_seen,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an iterator that filters and maps descendants from the starting `txid`.
|
/// Creates an iterator that filters and maps descendants from the starting `txid`.
|
||||||
///
|
///
|
||||||
/// The supplied closure takes in two inputs `(depth, descendant_txid)`:
|
/// The supplied closure takes in two inputs `(depth, descendant_txid)`:
|
||||||
@ -363,6 +377,9 @@ impl<A: Clone + Ord> TxGraph<A> {
|
|||||||
/// Returns the resultant [`Additions`] if the given `txout` is inserted at `outpoint`. Does not
|
/// Returns the resultant [`Additions`] if the given `txout` is inserted at `outpoint`. Does not
|
||||||
/// mutate `self`.
|
/// mutate `self`.
|
||||||
///
|
///
|
||||||
|
/// Inserting floating txouts are useful for determining fee/feerate of transactions we care
|
||||||
|
/// about.
|
||||||
|
///
|
||||||
/// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing
|
/// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing
|
||||||
/// the `outpoint`) already existed in `self`.
|
/// the `outpoint`) already existed in `self`.
|
||||||
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
|
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
|
||||||
@ -380,8 +397,10 @@ impl<A: Clone + Ord> TxGraph<A> {
|
|||||||
|
|
||||||
/// Inserts the given [`TxOut`] at [`OutPoint`].
|
/// Inserts the given [`TxOut`] at [`OutPoint`].
|
||||||
///
|
///
|
||||||
/// Note this will ignore the action if we already have the full transaction that the txout is
|
/// This is equivalent to calling [`insert_txout_preview`] and [`apply_additions`] in sequence.
|
||||||
/// alleged to be on (even if it doesn't match it!).
|
///
|
||||||
|
/// [`insert_txout_preview`]: Self::insert_txout_preview
|
||||||
|
/// [`apply_additions`]: Self::apply_additions
|
||||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
|
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
|
||||||
let additions = self.insert_txout_preview(outpoint, txout);
|
let additions = self.insert_txout_preview(outpoint, txout);
|
||||||
self.apply_additions(additions.clone());
|
self.apply_additions(additions.clone());
|
||||||
@ -581,7 +600,7 @@ impl<A: Clone + Ord> TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: BlockAnchor> TxGraph<A> {
|
impl<A: Anchor> TxGraph<A> {
|
||||||
/// Get all heights that are relevant to the graph.
|
/// Get all heights that are relevant to the graph.
|
||||||
pub fn relevant_heights(&self) -> impl Iterator<Item = u32> + '_ {
|
pub fn relevant_heights(&self) -> impl Iterator<Item = u32> + '_ {
|
||||||
let mut visited = HashSet::new();
|
let mut visited = HashSet::new();
|
||||||
@ -591,13 +610,21 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
.filter(move |&h| visited.insert(h))
|
.filter(move |&h| visited.insert(h))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines whether a transaction of `txid` is in the best chain.
|
/// Get the position of the transaction in `chain` with tip `chain_tip`.
|
||||||
///
|
///
|
||||||
/// TODO: Also return conflicting tx list, ordered by last_seen.
|
/// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is
|
||||||
|
/// returned.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
|
||||||
|
/// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
|
||||||
|
///
|
||||||
|
/// [`get_chain_position`]: Self::get_chain_position
|
||||||
pub fn try_get_chain_position<C>(
|
pub fn try_get_chain_position<C>(
|
||||||
&self,
|
&self,
|
||||||
chain: &C,
|
chain: &C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
) -> Result<Option<ObservedAs<&A>>, C::Error>
|
) -> Result<Option<ObservedAs<&A>>, C::Error>
|
||||||
where
|
where
|
||||||
@ -611,28 +638,27 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for anchor in anchors {
|
for anchor in anchors {
|
||||||
match chain.is_block_in_chain(anchor.anchor_block(), static_block)? {
|
match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
|
||||||
Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
|
Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
|
||||||
Some(false) => continue,
|
_ => continue,
|
||||||
// if we cannot determine whether block is in the best chain, we can check whether
|
}
|
||||||
// a spending transaction is confirmed in best chain, and if so, it is guaranteed
|
}
|
||||||
// that the tx being spent (this tx) is in the best chain
|
|
||||||
None => {
|
// If we cannot determine whether tx is in best chain, we can check whether a spending tx is
|
||||||
let spending_anchors = self
|
// confirmed and in best chain, and if so, it is guaranteed that this tx is in the best
|
||||||
.spends
|
// chain.
|
||||||
.range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
|
//
|
||||||
.flat_map(|(_, spending_txids)| spending_txids)
|
// [TODO] This logic is incomplete as we do not check spends of spends.
|
||||||
.filter_map(|spending_txid| self.txs.get(spending_txid))
|
let spending_anchors = self
|
||||||
.flat_map(|(_, spending_anchors, _)| spending_anchors);
|
.spends
|
||||||
for spending_anchor in spending_anchors {
|
.range(OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX))
|
||||||
match chain
|
.flat_map(|(_, spending_txids)| spending_txids)
|
||||||
.is_block_in_chain(spending_anchor.anchor_block(), static_block)?
|
.filter_map(|spending_txid| self.txs.get(spending_txid))
|
||||||
{
|
.flat_map(|(_, spending_anchors, _)| spending_anchors);
|
||||||
Some(true) => return Ok(Some(ObservedAs::Confirmed(anchor))),
|
for spending_anchor in spending_anchors {
|
||||||
_ => continue,
|
match chain.is_block_in_chain(spending_anchor.anchor_block(), chain_tip)? {
|
||||||
}
|
Some(true) => return Ok(Some(ObservedAs::ConfirmedImplicit(spending_anchor))),
|
||||||
}
|
_ => continue,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,7 +676,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
// this tx cannot exist in the best chain
|
// this tx cannot exist in the best chain
|
||||||
for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) {
|
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) {
|
for block in conflicting_tx.anchors.iter().map(A::anchor_block) {
|
||||||
if chain.is_block_in_chain(block, static_block)? == 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!
|
// conflicting tx is in best chain, so the current tx cannot be in best chain!
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@ -663,37 +689,54 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
Ok(Some(ObservedAs::Unconfirmed(last_seen)))
|
Ok(Some(ObservedAs::Unconfirmed(last_seen)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the position of the transaction in `chain` with tip `chain_tip`.
|
||||||
|
///
|
||||||
|
/// This is the infallible version of [`try_get_chain_position`].
|
||||||
|
///
|
||||||
|
/// [`try_get_chain_position`]: Self::try_get_chain_position
|
||||||
pub fn get_chain_position<C>(
|
pub fn get_chain_position<C>(
|
||||||
&self,
|
&self,
|
||||||
chain: &C,
|
chain: &C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
) -> Option<ObservedAs<&A>>
|
) -> Option<ObservedAs<&A>>
|
||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible>,
|
C: ChainOracle<Error = Infallible>,
|
||||||
{
|
{
|
||||||
self.try_get_chain_position(chain, static_block, txid)
|
self.try_get_chain_position(chain, chain_tip, txid)
|
||||||
.expect("error is infallible")
|
.expect("error is infallible")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_spend_in_chain<C>(
|
/// Get the txid of the spending transaction and where the spending transaction is observed in
|
||||||
|
/// the `chain` of `chain_tip`.
|
||||||
|
///
|
||||||
|
/// 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<C>(
|
||||||
&self,
|
&self,
|
||||||
chain: &C,
|
chain: &C,
|
||||||
static_block: BlockId,
|
chain_tip: BlockId,
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error>
|
) -> Result<Option<(ObservedAs<&A>, Txid)>, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
{
|
{
|
||||||
if self
|
if self
|
||||||
.try_get_chain_position(chain, static_block, outpoint.txid)?
|
.try_get_chain_position(chain, chain_tip, outpoint.txid)?
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
if let Some(spends) = self.spends.get(&outpoint) {
|
if let Some(spends) = self.spends.get(&outpoint) {
|
||||||
for &txid in spends {
|
for &txid in spends {
|
||||||
if let Some(observed_at) = self.try_get_chain_position(chain, static_block, txid)? {
|
if let Some(observed_at) = self.try_get_chain_position(chain, chain_tip, txid)? {
|
||||||
return Ok(Some((observed_at, txid)));
|
return Ok(Some((observed_at, txid)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -701,6 +744,12 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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_chain_spend`]
|
||||||
|
///
|
||||||
|
/// [`try_get_chain_spend`]: Self::try_get_chain_spend
|
||||||
pub fn get_chain_spend<C>(
|
pub fn get_chain_spend<C>(
|
||||||
&self,
|
&self,
|
||||||
chain: &C,
|
chain: &C,
|
||||||
@ -710,9 +759,186 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
where
|
where
|
||||||
C: ChainOracle<Error = Infallible>,
|
C: ChainOracle<Error = Infallible>,
|
||||||
{
|
{
|
||||||
self.try_get_spend_in_chain(chain, static_block, outpoint)
|
self.try_get_chain_spend(chain, static_block, outpoint)
|
||||||
.expect("error is infallible")
|
.expect("error is infallible")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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_chain_txs`] can be used instead.
|
||||||
|
///
|
||||||
|
/// [`list_chain_txs`]: Self::list_chain_txs
|
||||||
|
pub fn try_list_chain_txs<'a, C>(
|
||||||
|
&'a self,
|
||||||
|
chain: &'a C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
|
||||||
|
where
|
||||||
|
C: ChainOracle + 'a,
|
||||||
|
{
|
||||||
|
self.full_txs().filter_map(move |tx| {
|
||||||
|
self.try_get_chain_position(chain, chain_tip, tx.txid)
|
||||||
|
.map(|v| {
|
||||||
|
v.map(|observed_in| CanonicalTx {
|
||||||
|
observed_as: observed_in,
|
||||||
|
node: tx,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List graph transactions that are in `chain` with `chain_tip`.
|
||||||
|
///
|
||||||
|
/// 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>(
|
||||||
|
&'a self,
|
||||||
|
chain: &'a C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
|
||||||
|
where
|
||||||
|
C: ChainOracle + 'a,
|
||||||
|
{
|
||||||
|
self.try_list_chain_txs(chain, chain_tip)
|
||||||
|
.map(|r| r.expect("oracle is infallible"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List outputs that are in `chain` with `chain_tip`.
|
||||||
|
///
|
||||||
|
/// Floating ouputs are not iterated over.
|
||||||
|
///
|
||||||
|
/// The `filter_predicate` should return true for outputs that we wish to iterate over.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// A returned item can error if the [`ChainOracle`] implementation (`chain`) fails.
|
||||||
|
///
|
||||||
|
/// If the [`ChainOracle`] is infallible, [`list_chain_txouts`] can be used instead.
|
||||||
|
///
|
||||||
|
/// [`list_chain_txouts`]: Self::list_chain_txouts
|
||||||
|
pub fn try_list_chain_txouts<'a, C, P>(
|
||||||
|
&'a self,
|
||||||
|
chain: &'a C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
mut filter_predicate: P,
|
||||||
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||||
|
where
|
||||||
|
C: ChainOracle + 'a,
|
||||||
|
P: FnMut(OutPoint, &TxOut) -> bool + 'a,
|
||||||
|
{
|
||||||
|
self.try_list_chain_txs(chain, chain_tip)
|
||||||
|
.flat_map(move |tx_res| match tx_res {
|
||||||
|
Ok(canonical_tx) => canonical_tx
|
||||||
|
.node
|
||||||
|
.output
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(vout, txout)| {
|
||||||
|
let outpoint = OutPoint::new(canonical_tx.node.txid, vout as _);
|
||||||
|
if filter_predicate(outpoint, txout) {
|
||||||
|
Some(Ok((outpoint, txout.clone(), canonical_tx.clone())))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
Err(err) => vec![Err(err)],
|
||||||
|
})
|
||||||
|
.map(move |res| -> Result<_, C::Error> {
|
||||||
|
let (
|
||||||
|
outpoint,
|
||||||
|
txout,
|
||||||
|
CanonicalTx {
|
||||||
|
observed_as,
|
||||||
|
node: tx_node,
|
||||||
|
},
|
||||||
|
) = res?;
|
||||||
|
let chain_position = observed_as.cloned();
|
||||||
|
let spent_by = self
|
||||||
|
.try_get_chain_spend(chain, chain_tip, outpoint)?
|
||||||
|
.map(|(obs_as, txid)| (obs_as.cloned(), txid));
|
||||||
|
let is_on_coinbase = tx_node.tx.is_coin_base();
|
||||||
|
Ok(FullTxOut {
|
||||||
|
outpoint,
|
||||||
|
txout,
|
||||||
|
chain_position,
|
||||||
|
spent_by,
|
||||||
|
is_on_coinbase,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List outputs that are in `chain` with `chain_tip`.
|
||||||
|
///
|
||||||
|
/// This is the infallible version of [`try_list_chain_txouts`].
|
||||||
|
///
|
||||||
|
/// [`try_list_chain_txouts`]: Self::try_list_chain_txouts
|
||||||
|
pub fn list_chain_txouts<'a, C, P>(
|
||||||
|
&'a self,
|
||||||
|
chain: &'a C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
filter_predicate: P,
|
||||||
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||||
|
where
|
||||||
|
C: ChainOracle<Error = Infallible> + 'a,
|
||||||
|
P: FnMut(OutPoint, &TxOut) -> bool + 'a,
|
||||||
|
{
|
||||||
|
self.try_list_chain_txouts(chain, chain_tip, filter_predicate)
|
||||||
|
.map(|r| r.expect("error in infallible"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List unspent outputs (UTXOs) that are in `chain` with `chain_tip`.
|
||||||
|
///
|
||||||
|
/// Floating outputs are not iterated over.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// An item can be an error if the [`ChainOracle`] implementation fails. If the oracle is
|
||||||
|
/// infallible, [`list_chain_unspents`] can be used instead.
|
||||||
|
///
|
||||||
|
/// [`list_chain_unspents`]: Self::list_chain_unspents
|
||||||
|
pub fn try_list_chain_unspents<'a, C, P>(
|
||||||
|
&'a self,
|
||||||
|
chain: &'a C,
|
||||||
|
chain_tip: BlockId,
|
||||||
|
filter_txout: P,
|
||||||
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
||||||
|
where
|
||||||
|
C: ChainOracle + 'a,
|
||||||
|
P: FnMut(OutPoint, &TxOut) -> bool + 'a,
|
||||||
|
{
|
||||||
|
self.try_list_chain_txouts(chain, chain_tip, filter_txout)
|
||||||
|
.filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List unspent outputs (UTXOs) that are in `chain` with `chain_tip`.
|
||||||
|
///
|
||||||
|
/// This is the infallible version of [`try_list_chain_unspents`].
|
||||||
|
///
|
||||||
|
/// [`try_list_chain_unspents`]: Self::try_list_chain_unspents
|
||||||
|
pub fn list_chain_unspents<'a, C, P>(
|
||||||
|
&'a self,
|
||||||
|
chain: &'a C,
|
||||||
|
static_block: BlockId,
|
||||||
|
filter_txout: P,
|
||||||
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
||||||
|
where
|
||||||
|
C: ChainOracle<Error = Infallible> + 'a,
|
||||||
|
P: FnMut(OutPoint, &TxOut) -> bool + 'a,
|
||||||
|
{
|
||||||
|
self.try_list_chain_unspents(chain, static_block, filter_txout)
|
||||||
|
.map(|r| r.expect("error is infallible"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure that represents changes to a [`TxGraph`].
|
/// A structure that represents changes to a [`TxGraph`].
|
||||||
|
@ -149,12 +149,12 @@ fn insert_txouts() {
|
|||||||
// Apply addition and check the new graph counts.
|
// Apply addition and check the new graph counts.
|
||||||
graph.apply_additions(additions);
|
graph.apply_additions(additions);
|
||||||
assert_eq!(graph.all_txouts().count(), 4);
|
assert_eq!(graph.all_txouts().count(), 4);
|
||||||
assert_eq!(graph.full_transactions().count(), 1);
|
assert_eq!(graph.full_txs().count(), 1);
|
||||||
assert_eq!(graph.partial_transactions().count(), 2);
|
assert_eq!(graph.floating_txouts().count(), 3);
|
||||||
|
|
||||||
// Check TxOuts are fetched correctly from the graph.
|
// Check TxOuts are fetched correctly from the graph.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph.txouts(h!("tx1")).expect("should exists"),
|
graph.tx_outputs(h!("tx1")).expect("should exists"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
1u32,
|
1u32,
|
||||||
@ -175,7 +175,7 @@ fn insert_txouts() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph.txouts(update_txs.txid()).expect("should exists"),
|
graph.tx_outputs(update_txs.txid()).expect("should exists"),
|
||||||
[(
|
[(
|
||||||
0u32,
|
0u32,
|
||||||
&TxOut {
|
&TxOut {
|
||||||
@ -201,8 +201,8 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
|
|||||||
|
|
||||||
let mut graph = TxGraph::<()>::default();
|
let mut graph = TxGraph::<()>::default();
|
||||||
let _ = graph.insert_tx(tx);
|
let _ = graph.insert_tx(tx);
|
||||||
assert!(graph.outspends(OutPoint::null()).is_empty());
|
assert!(graph.output_spends(OutPoint::null()).is_empty());
|
||||||
assert!(graph.tx_outspends(Txid::all_zeros()).next().is_none());
|
assert!(graph.tx_spends(Txid::all_zeros()).next().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -240,10 +240,10 @@ fn insert_tx_graph_keeps_track_of_spend() {
|
|||||||
let _ = graph2.insert_tx(tx1);
|
let _ = graph2.insert_tx(tx1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph1.outspends(op),
|
graph1.output_spends(op),
|
||||||
&iter::once(tx2.txid()).collect::<HashSet<_>>()
|
&iter::once(tx2.txid()).collect::<HashSet<_>>()
|
||||||
);
|
);
|
||||||
assert_eq!(graph2.outspends(op), graph1.outspends(op));
|
assert_eq!(graph2.output_spends(op), graph1.output_spends(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user