[bdk_chain_redesign] Add ..in_chain
methods
Add methods to `TxGraph` and `IndexedTxGraph` that gets in-best-chain data (such as transactions, txouts, unspent txouts).
This commit is contained in:
parent
61a8606fbc
commit
43b648fee0
@ -24,7 +24,7 @@ use bdk_chain::{
|
|||||||
chain_graph,
|
chain_graph,
|
||||||
keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
|
keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
|
||||||
sparse_chain,
|
sparse_chain,
|
||||||
tx_graph::GraphedTx,
|
tx_graph::TxInGraph,
|
||||||
BlockId, ConfirmationTime,
|
BlockId, ConfirmationTime,
|
||||||
};
|
};
|
||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
@ -524,7 +524,7 @@ impl<D> Wallet<D> {
|
|||||||
/// unconfirmed transactions last.
|
/// unconfirmed transactions last.
|
||||||
pub fn transactions(
|
pub fn transactions(
|
||||||
&self,
|
&self,
|
||||||
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, GraphedTx<'_, Transaction, BlockId>)> + '_
|
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, TxInGraph<'_, Transaction, BlockId>)> + '_
|
||||||
{
|
{
|
||||||
self.keychain_tracker
|
self.keychain_tracker
|
||||||
.chain_graph()
|
.chain_graph()
|
||||||
|
@ -6,12 +6,41 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Represents an observation of some chain data.
|
/// Represents an observation of some chain data.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
|
||||||
pub enum Observation<A> {
|
pub enum ObservedIn<A> {
|
||||||
/// The chain data is seen in a block identified by `A`.
|
/// The chain data is seen in a block identified by `A`.
|
||||||
InBlock(A),
|
Block(A),
|
||||||
/// The chain data is seen at this given unix timestamp.
|
/// The chain data is seen in mempool at this given timestamp.
|
||||||
SeenAt(u64),
|
Mempool(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChainPosition for ObservedIn<BlockId> {
|
||||||
|
fn height(&self) -> TxHeight {
|
||||||
|
match self {
|
||||||
|
ObservedIn::Block(block_id) => TxHeight::Confirmed(block_id.height),
|
||||||
|
ObservedIn::Mempool(_) => TxHeight::Unconfirmed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_ord_of_height(height: TxHeight) -> Self {
|
||||||
|
match height {
|
||||||
|
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
|
||||||
|
height,
|
||||||
|
hash: Hash::from_inner([u8::MAX; 32]),
|
||||||
|
}),
|
||||||
|
TxHeight::Unconfirmed => Self::Mempool(u64::MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_ord_of_height(height: TxHeight) -> Self {
|
||||||
|
match height {
|
||||||
|
TxHeight::Confirmed(height) => ObservedIn::Block(BlockId {
|
||||||
|
height,
|
||||||
|
hash: Hash::from_inner([u8::MIN; 32]),
|
||||||
|
}),
|
||||||
|
TxHeight::Unconfirmed => Self::Mempool(u64::MIN),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the height at which a transaction is confirmed.
|
/// Represents the height at which a transaction is confirmed.
|
||||||
@ -177,7 +206,7 @@ impl From<(&u32, &BlockHash)> for BlockId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A `TxOut` with as much data as we can retrieve about it
|
/// A `TxOut` with as much data as we can retrieve about it
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct FullTxOut<I> {
|
pub struct FullTxOut<I> {
|
||||||
/// The location of the `TxOut`.
|
/// The location of the `TxOut`.
|
||||||
pub outpoint: OutPoint,
|
pub outpoint: OutPoint,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sparse_chain::{self, ChainPosition, SparseChain},
|
sparse_chain::{self, ChainPosition, SparseChain},
|
||||||
tx_graph::{self, GraphedTx, TxGraph},
|
tx_graph::{self, TxGraph, TxInGraph},
|
||||||
BlockAnchor, BlockId, ForEachTxOut, FullTxOut, TxHeight,
|
BlockAnchor, BlockId, ForEachTxOut, FullTxOut, TxHeight,
|
||||||
};
|
};
|
||||||
use alloc::{string::ToString, vec::Vec};
|
use alloc::{string::ToString, vec::Vec};
|
||||||
@ -213,7 +213,7 @@ where
|
|||||||
///
|
///
|
||||||
/// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in
|
/// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in
|
||||||
/// the unconfirmed transaction list within the [`SparseChain`].
|
/// the unconfirmed transaction list within the [`SparseChain`].
|
||||||
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, GraphedTx<'_, Transaction, A>)> {
|
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, TxInGraph<'_, Transaction, A>)> {
|
||||||
let position = self.chain.tx_position(txid)?;
|
let position = self.chain.tx_position(txid)?;
|
||||||
let graphed_tx = self.graph.get_tx(txid).expect("must exist");
|
let graphed_tx = self.graph.get_tx(txid).expect("must exist");
|
||||||
Some((position, graphed_tx))
|
Some((position, graphed_tx))
|
||||||
@ -441,7 +441,7 @@ where
|
|||||||
/// in ascending order.
|
/// in ascending order.
|
||||||
pub fn transactions_in_chain(
|
pub fn transactions_in_chain(
|
||||||
&self,
|
&self,
|
||||||
) -> impl DoubleEndedIterator<Item = (&P, GraphedTx<'_, Transaction, A>)> {
|
) -> impl DoubleEndedIterator<Item = (&P, TxInGraph<'_, Transaction, A>)> {
|
||||||
self.chain
|
self.chain
|
||||||
.txids()
|
.txids()
|
||||||
.map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
|
.map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
|
||||||
|
@ -88,9 +88,11 @@ impl<K> Deref for KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug> TxIndex for KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug + 'static> TxIndex for KeychainTxOutIndex<K> {
|
||||||
type Additions = DerivationAdditions<K>;
|
type Additions = DerivationAdditions<K>;
|
||||||
|
|
||||||
|
type SpkIndex = (K, u32);
|
||||||
|
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
||||||
self.scan_txout(outpoint, txout)
|
self.scan_txout(outpoint, txout)
|
||||||
}
|
}
|
||||||
@ -102,6 +104,10 @@ impl<K: Clone + Ord + Debug> TxIndex for KeychainTxOutIndex<K> {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn relevant_txouts(&self) -> &BTreeMap<OutPoint, (Self::SpkIndex, TxOut)> {
|
||||||
|
self.inner.relevant_txouts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
||||||
|
@ -52,9 +52,11 @@ impl<I> Default for SpkTxOutIndex<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Clone + Ord> TxIndex for SpkTxOutIndex<I> {
|
impl<I: Clone + Ord + 'static> TxIndex for SpkTxOutIndex<I> {
|
||||||
type Additions = BTreeSet<I>;
|
type Additions = BTreeSet<I>;
|
||||||
|
|
||||||
|
type SpkIndex = I;
|
||||||
|
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions {
|
||||||
self.scan_txout(outpoint, txout)
|
self.scan_txout(outpoint, txout)
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -69,6 +71,10 @@ impl<I: Clone + Ord> TxIndex for SpkTxOutIndex<I> {
|
|||||||
fn is_tx_relevant(&self, tx: &Transaction) -> bool {
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool {
|
||||||
self.is_relevant(tx)
|
self.is_relevant(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn relevant_txouts(&self) -> &BTreeMap<OutPoint, (Self::SpkIndex, TxOut)> {
|
||||||
|
&self.txouts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::{BTreeMap, BTreeSet};
|
||||||
use bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};
|
use bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};
|
||||||
|
|
||||||
use crate::BlockId;
|
use crate::BlockId;
|
||||||
@ -100,6 +100,8 @@ pub trait TxIndex {
|
|||||||
/// The resultant "additions" when new transaction data is indexed.
|
/// The resultant "additions" when new transaction data is indexed.
|
||||||
type Additions: TxIndexAdditions;
|
type Additions: TxIndexAdditions;
|
||||||
|
|
||||||
|
type SpkIndex: Ord;
|
||||||
|
|
||||||
/// Scan and index the given `outpoint` and `txout`.
|
/// Scan and index the given `outpoint` and `txout`.
|
||||||
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions;
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions;
|
||||||
|
|
||||||
@ -120,4 +122,7 @@ pub trait TxIndex {
|
|||||||
/// A transaction is relevant if it contains a txout with a script_pubkey that we own, or if it
|
/// A transaction is relevant if it contains a txout with a script_pubkey that we own, or if it
|
||||||
/// spends an already-indexed outpoint that we have previously indexed.
|
/// spends an already-indexed outpoint that we have previously indexed.
|
||||||
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
||||||
|
|
||||||
|
/// Lists all relevant txouts known by the index.
|
||||||
|
fn relevant_txouts(&self) -> &BTreeMap<OutPoint, (Self::SpkIndex, TxOut)>;
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,8 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
collections::*, BlockAnchor, BlockId, ChainOracle, ForEachTxOut, Observation, TxIndex,
|
collections::*, sparse_chain::ChainPosition, BlockAnchor, BlockId, ChainOracle, ForEachTxOut,
|
||||||
TxIndexAdditions,
|
FullTxOut, ObservedIn, TxIndex, TxIndexAdditions,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||||
@ -91,9 +91,12 @@ impl<A> Default for TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub type InChainTx<'a, T, A> = (ObservedIn<&'a A>, TxInGraph<'a, T, A>);
|
||||||
|
// pub type InChainTxOut<'a, I, A> = (&'a I, FullTxOut<ObservedIn<&'a A>>);
|
||||||
|
|
||||||
/// An outward-facing view of a transaction that resides in a [`TxGraph`].
|
/// An outward-facing view of a transaction that resides in a [`TxGraph`].
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct GraphedTx<'a, T, A> {
|
pub struct TxInGraph<'a, T, A> {
|
||||||
/// Txid of the transaction.
|
/// Txid of the transaction.
|
||||||
pub txid: Txid,
|
pub txid: Txid,
|
||||||
/// A partial or full representation of the transaction.
|
/// A partial or full representation of the transaction.
|
||||||
@ -104,7 +107,7 @@ pub struct GraphedTx<'a, T, A> {
|
|||||||
pub last_seen: u64,
|
pub last_seen: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, A> Deref for GraphedTx<'a, T, A> {
|
impl<'a, T, A> Deref for TxInGraph<'a, T, A> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@ -112,7 +115,7 @@ impl<'a, T, A> Deref for GraphedTx<'a, T, A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A> GraphedTx<'a, Transaction, A> {
|
impl<'a, A> TxInGraph<'a, Transaction, A> {
|
||||||
pub fn from_tx(tx: &'a Transaction, anchors: &'a BTreeSet<A>) -> Self {
|
pub fn from_tx(tx: &'a Transaction, anchors: &'a BTreeSet<A>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
txid: tx.txid(),
|
txid: tx.txid(),
|
||||||
@ -123,6 +126,18 @@ impl<'a, A> GraphedTx<'a, Transaction, A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct TxInChain<'a, T, A> {
|
||||||
|
pub observed_in: ObservedIn<&'a A>,
|
||||||
|
pub tx: TxInGraph<'a, T, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct TxOutInChain<'a, I, A> {
|
||||||
|
pub spk_index: &'a I,
|
||||||
|
pub txout: FullTxOut<ObservedIn<&'a A>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal representation of a transaction node of a [`TxGraph`].
|
/// Internal representation of a transaction node of a [`TxGraph`].
|
||||||
///
|
///
|
||||||
/// This can either be a whole transaction, or a partial transaction (where we only have select
|
/// This can either be a whole transaction, or a partial transaction (where we only have select
|
||||||
@ -157,11 +172,11 @@ impl<A> TxGraph<A> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all full transactions in the graph.
|
/// Iterate over all full transactions in the graph.
|
||||||
pub fn full_transactions(&self) -> impl Iterator<Item = GraphedTx<'_, Transaction, A>> {
|
pub fn full_transactions(&self) -> impl Iterator<Item = TxInGraph<'_, 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 {
|
||||||
TxNode::Whole(tx) => Some(GraphedTx {
|
TxNode::Whole(tx) => Some(TxInGraph {
|
||||||
txid,
|
txid,
|
||||||
tx,
|
tx,
|
||||||
anchors,
|
anchors,
|
||||||
@ -176,9 +191,9 @@ impl<A> TxGraph<A> {
|
|||||||
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
|
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
|
||||||
///
|
///
|
||||||
/// [`get_txout`]: Self::get_txout
|
/// [`get_txout`]: Self::get_txout
|
||||||
pub fn get_tx(&self, txid: Txid) -> Option<GraphedTx<'_, Transaction, A>> {
|
pub fn get_tx(&self, txid: Txid) -> Option<TxInGraph<'_, Transaction, A>> {
|
||||||
match &self.txs.get(&txid)? {
|
match &self.txs.get(&txid)? {
|
||||||
(TxNode::Whole(tx), anchors, last_seen) => Some(GraphedTx {
|
(TxNode::Whole(tx), anchors, last_seen) => Some(TxInGraph {
|
||||||
txid,
|
txid,
|
||||||
tx,
|
tx,
|
||||||
anchors,
|
anchors,
|
||||||
@ -212,12 +227,6 @@ impl<A> TxGraph<A> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_anchors_and_last_seen(&self, txid: Txid) -> Option<(&BTreeSet<A>, u64)> {
|
|
||||||
self.txs
|
|
||||||
.get(&txid)
|
|
||||||
.map(|(_, anchors, last_seen)| (anchors, *last_seen))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
|
/// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
|
||||||
/// Returns `Some(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
|
/// Returns `Some(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
|
||||||
/// the full transactions or individual txouts). If the returned value is negative, then the
|
/// the full transactions or individual txouts). If the returned value is negative, then the
|
||||||
@ -472,10 +481,22 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
self.determine_additions(&update)
|
self.determine_additions(&update)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all heights that are relevant to the graph.
|
||||||
|
pub fn relevant_heights(&self) -> BTreeSet<u32> {
|
||||||
|
self.anchors
|
||||||
|
.iter()
|
||||||
|
.map(|(a, _)| a.anchor_block().height)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Determines whether a transaction of `txid` is in the best chain.
|
/// Determines whether a transaction of `txid` is in the best chain.
|
||||||
///
|
///
|
||||||
/// TODO: Also return conflicting tx list, ordered by last_seen.
|
/// TODO: Also return conflicting tx list, ordered by last_seen.
|
||||||
pub fn is_txid_in_best_chain<C>(&self, chain: C, txid: Txid) -> Result<bool, C::Error>
|
pub fn get_position_in_chain<C>(
|
||||||
|
&self,
|
||||||
|
chain: C,
|
||||||
|
txid: Txid,
|
||||||
|
) -> Result<Option<ObservedIn<&A>>, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
{
|
{
|
||||||
@ -483,12 +504,12 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
Some((tx, anchors, last_seen)) if !(anchors.is_empty() && *last_seen == 0) => {
|
Some((tx, anchors, last_seen)) if !(anchors.is_empty() && *last_seen == 0) => {
|
||||||
(tx, anchors, last_seen)
|
(tx, anchors, last_seen)
|
||||||
}
|
}
|
||||||
_ => return Ok(false),
|
_ => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
for block_id in anchors.iter().map(A::anchor_block) {
|
for anchor in anchors {
|
||||||
if chain.is_block_in_best_chain(block_id)? {
|
if chain.is_block_in_best_chain(anchor.anchor_block())? {
|
||||||
return Ok(true);
|
return Ok(Some(ObservedIn::Block(anchor)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,7 +520,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
TxNode::Partial(_) => {
|
TxNode::Partial(_) => {
|
||||||
// [TODO] Unfortunately, we can't iterate over conflicts of partial txs right now!
|
// [TODO] Unfortunately, we can't iterate over conflicts of partial txs right now!
|
||||||
// [TODO] So we just assume the partial tx does not exist in the best chain :/
|
// [TODO] So we just assume the partial tx does not exist in the best chain :/
|
||||||
return Ok(false);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -509,7 +530,7 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
for block_id in conflicting_tx.anchors.iter().map(A::anchor_block) {
|
for block_id in conflicting_tx.anchors.iter().map(A::anchor_block) {
|
||||||
if chain.is_block_in_best_chain(block_id)? {
|
if chain.is_block_in_best_chain(block_id)? {
|
||||||
// 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(false);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if conflicting_tx.last_seen > latest_last_seen {
|
if conflicting_tx.last_seen > latest_last_seen {
|
||||||
@ -517,28 +538,47 @@ impl<A: BlockAnchor> TxGraph<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if last_seen >= latest_last_seen {
|
if last_seen >= latest_last_seen {
|
||||||
Ok(true)
|
Ok(Some(ObservedIn::Mempool(last_seen)))
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if `outpoint` exists in best chain and is unspent.
|
pub fn get_spend_in_chain<C>(
|
||||||
pub fn is_unspent<C>(&self, chain: C, outpoint: OutPoint) -> Result<bool, C::Error>
|
&self,
|
||||||
|
chain: C,
|
||||||
|
outpoint: OutPoint,
|
||||||
|
) -> Result<Option<(ObservedIn<&A>, Txid)>, C::Error>
|
||||||
where
|
where
|
||||||
C: ChainOracle,
|
C: ChainOracle,
|
||||||
{
|
{
|
||||||
if !self.is_txid_in_best_chain(&chain, outpoint.txid)? {
|
if self.get_position_in_chain(&chain, outpoint.txid)?.is_none() {
|
||||||
return Ok(false);
|
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 self.is_txid_in_best_chain(&chain, txid)? {
|
if let Some(observed_at) = self.get_position_in_chain(&chain, txid)? {
|
||||||
return Ok(false);
|
return Ok(Some((observed_at, txid)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transactions_in_chain<C>(
|
||||||
|
&self,
|
||||||
|
chain: C,
|
||||||
|
) -> Result<BTreeSet<TxInChain<'_, Transaction, A>>, C::Error>
|
||||||
|
where
|
||||||
|
C: ChainOracle,
|
||||||
|
{
|
||||||
|
self.full_transactions()
|
||||||
|
.filter_map(|tx| {
|
||||||
|
self.get_position_in_chain(&chain, tx.txid)
|
||||||
|
.map(|v| v.map(|observed_in| TxInChain { observed_in, tx }))
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,12 +614,12 @@ impl<A> TxGraph<A> {
|
|||||||
/// Iterate over all partial transactions (outputs only) in the graph.
|
/// Iterate over all partial transactions (outputs only) in the graph.
|
||||||
pub fn partial_transactions(
|
pub fn partial_transactions(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = GraphedTx<'_, BTreeMap<u32, TxOut>, A>> {
|
) -> impl Iterator<Item = TxInGraph<'_, BTreeMap<u32, TxOut>, 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 {
|
||||||
TxNode::Whole(_) => None,
|
TxNode::Whole(_) => None,
|
||||||
TxNode::Partial(partial) => Some(GraphedTx {
|
TxNode::Partial(partial) => Some(TxInGraph {
|
||||||
txid,
|
txid,
|
||||||
tx: partial,
|
tx: partial,
|
||||||
anchors,
|
anchors,
|
||||||
@ -686,18 +726,29 @@ impl<A, I: Default> Default for IndexedTxGraph<A, I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
||||||
|
/// Get a reference of the internal transaction graph.
|
||||||
|
pub fn graph(&self) -> &TxGraph<A> {
|
||||||
|
&self.graph
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference of the internal transaction index.
|
||||||
|
pub fn index(&self) -> &I {
|
||||||
|
&self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a `txout` that exists in `outpoint` with the given `observation`.
|
||||||
pub fn insert_txout(
|
pub fn insert_txout(
|
||||||
&mut self,
|
&mut self,
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
txout: &TxOut,
|
txout: &TxOut,
|
||||||
observation: Observation<A>,
|
observation: ObservedIn<A>,
|
||||||
) -> IndexedAdditions<A, I::Additions> {
|
) -> IndexedAdditions<A, I::Additions> {
|
||||||
IndexedAdditions {
|
IndexedAdditions {
|
||||||
graph_additions: {
|
graph_additions: {
|
||||||
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
|
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
|
||||||
graph_additions.append(match observation {
|
graph_additions.append(match observation {
|
||||||
Observation::InBlock(anchor) => self.graph.insert_anchor(outpoint.txid, anchor),
|
ObservedIn::Block(anchor) => self.graph.insert_anchor(outpoint.txid, anchor),
|
||||||
Observation::SeenAt(seen_at) => {
|
ObservedIn::Mempool(seen_at) => {
|
||||||
self.graph.insert_seen_at(outpoint.txid, seen_at)
|
self.graph.insert_seen_at(outpoint.txid, seen_at)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -710,15 +761,15 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn insert_tx(
|
pub fn insert_tx(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
observation: Observation<A>,
|
observation: ObservedIn<A>,
|
||||||
) -> IndexedAdditions<A, I::Additions> {
|
) -> IndexedAdditions<A, I::Additions> {
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
IndexedAdditions {
|
IndexedAdditions {
|
||||||
graph_additions: {
|
graph_additions: {
|
||||||
let mut graph_additions = self.graph.insert_tx(tx.clone());
|
let mut graph_additions = self.graph.insert_tx(tx.clone());
|
||||||
graph_additions.append(match observation {
|
graph_additions.append(match observation {
|
||||||
Observation::InBlock(anchor) => self.graph.insert_anchor(txid, anchor),
|
ObservedIn::Block(anchor) => self.graph.insert_anchor(txid, anchor),
|
||||||
Observation::SeenAt(seen_at) => self.graph.insert_seen_at(txid, seen_at),
|
ObservedIn::Mempool(seen_at) => self.graph.insert_seen_at(txid, seen_at),
|
||||||
});
|
});
|
||||||
graph_additions
|
graph_additions
|
||||||
},
|
},
|
||||||
@ -729,7 +780,7 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
pub fn filter_and_insert_txs<'t, T>(
|
pub fn filter_and_insert_txs<'t, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
txs: T,
|
txs: T,
|
||||||
observation: Observation<A>,
|
observation: ObservedIn<A>,
|
||||||
) -> IndexedAdditions<A, I::Additions>
|
) -> IndexedAdditions<A, I::Additions>
|
||||||
where
|
where
|
||||||
T: Iterator<Item = &'t Transaction>,
|
T: Iterator<Item = &'t Transaction>,
|
||||||
@ -746,6 +797,81 @@ impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|||||||
acc
|
acc
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn relevant_heights(&self) -> BTreeSet<u32> {
|
||||||
|
self.graph.relevant_heights()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txs_in_chain<C>(
|
||||||
|
&self,
|
||||||
|
chain: C,
|
||||||
|
) -> Result<BTreeSet<TxInChain<'_, Transaction, A>>, C::Error>
|
||||||
|
where
|
||||||
|
C: ChainOracle,
|
||||||
|
{
|
||||||
|
let mut tx_set = self.graph.transactions_in_chain(chain)?;
|
||||||
|
tx_set.retain(|tx| self.index.is_tx_relevant(&tx.tx));
|
||||||
|
Ok(tx_set)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn txouts_in_chain<C>(
|
||||||
|
&self,
|
||||||
|
chain: C,
|
||||||
|
) -> Result<Vec<TxOutInChain<'_, I::SpkIndex, A>>, C::Error>
|
||||||
|
where
|
||||||
|
C: ChainOracle,
|
||||||
|
ObservedIn<A>: ChainPosition,
|
||||||
|
{
|
||||||
|
self.index
|
||||||
|
.relevant_txouts()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(op, (spk_i, 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.get_position_in_chain(&chain, op.txid) {
|
||||||
|
Ok(Some(observed_at)) => observed_at,
|
||||||
|
Ok(None) => return None,
|
||||||
|
Err(err) => return Some(Err(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let spent_by = match self.graph.get_spend_in_chain(&chain, *op) {
|
||||||
|
Ok(spent_by) => spent_by,
|
||||||
|
Err(err) => return Some(Err(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_txout = FullTxOut {
|
||||||
|
outpoint: *op,
|
||||||
|
txout: txout.clone(),
|
||||||
|
chain_position,
|
||||||
|
spent_by,
|
||||||
|
is_on_coinbase,
|
||||||
|
};
|
||||||
|
|
||||||
|
let txout_in_chain = TxOutInChain {
|
||||||
|
spk_index: spk_i,
|
||||||
|
txout: full_txout,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Ok(txout_in_chain))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return relevant unspents.
|
||||||
|
pub fn utxos_in_chain<C>(
|
||||||
|
&self,
|
||||||
|
chain: C,
|
||||||
|
) -> Result<Vec<TxOutInChain<'_, I::SpkIndex, A>>, C::Error>
|
||||||
|
where
|
||||||
|
C: ChainOracle,
|
||||||
|
ObservedIn<A>: ChainPosition,
|
||||||
|
{
|
||||||
|
let mut txouts = self.txouts_in_chain(chain)?;
|
||||||
|
txouts.retain(|txo| txo.txout.spent_by.is_none());
|
||||||
|
Ok(txouts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure that represents changes to a [`TxGraph`].
|
/// A structure that represents changes to a [`TxGraph`].
|
||||||
|
@ -7,7 +7,7 @@ use bdk_chain::{
|
|||||||
chain_graph::*,
|
chain_graph::*,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sparse_chain,
|
sparse_chain,
|
||||||
tx_graph::{self, GraphedTx, TxGraph},
|
tx_graph::{self, TxGraph, TxInGraph},
|
||||||
BlockId, TxHeight,
|
BlockId, TxHeight,
|
||||||
};
|
};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
@ -371,7 +371,7 @@ fn test_get_tx_in_chain() {
|
|||||||
cg.get_tx_in_chain(tx.txid()),
|
cg.get_tx_in_chain(tx.txid()),
|
||||||
Some((
|
Some((
|
||||||
&TxHeight::Unconfirmed,
|
&TxHeight::Unconfirmed,
|
||||||
GraphedTx {
|
TxInGraph {
|
||||||
txid: tx.txid(),
|
txid: tx.txid(),
|
||||||
tx: &tx,
|
tx: &tx,
|
||||||
anchors: &BTreeSet::new(),
|
anchors: &BTreeSet::new(),
|
||||||
@ -411,15 +411,15 @@ fn test_iterate_transactions() {
|
|||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
&TxHeight::Confirmed(0),
|
&TxHeight::Confirmed(0),
|
||||||
GraphedTx::from_tx(&txs[2], &BTreeSet::new())
|
TxInGraph::from_tx(&txs[2], &BTreeSet::new())
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
&TxHeight::Confirmed(1),
|
&TxHeight::Confirmed(1),
|
||||||
GraphedTx::from_tx(&txs[0], &BTreeSet::new())
|
TxInGraph::from_tx(&txs[0], &BTreeSet::new())
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
&TxHeight::Unconfirmed,
|
&TxHeight::Unconfirmed,
|
||||||
GraphedTx::from_tx(&txs[1], &BTreeSet::new())
|
TxInGraph::from_tx(&txs[1], &BTreeSet::new())
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ use bdk_chain::{
|
|||||||
bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut},
|
bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut},
|
||||||
Descriptor,
|
Descriptor,
|
||||||
},
|
},
|
||||||
tx_graph::GraphedTx,
|
tx_graph::TxInGraph,
|
||||||
BlockId, ConfirmationTime, TxHeight,
|
BlockId, ConfirmationTime, TxHeight,
|
||||||
};
|
};
|
||||||
use bitcoin::{BlockHash, TxIn};
|
use bitcoin::{BlockHash, TxIn};
|
||||||
@ -45,7 +45,7 @@ fn test_insert_tx() {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![(
|
vec![(
|
||||||
&ConfirmationTime::Unconfirmed,
|
&ConfirmationTime::Unconfirmed,
|
||||||
GraphedTx::from_tx(&tx, &BTreeSet::new())
|
TxInGraph::from_tx(&tx, &BTreeSet::new())
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
mod common;
|
mod common;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
collections::*,
|
collections::*,
|
||||||
tx_graph::{Additions, GraphedTx, TxGraph},
|
tx_graph::{Additions, TxGraph, TxInGraph},
|
||||||
BlockId,
|
BlockId,
|
||||||
};
|
};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
@ -157,7 +157,7 @@ fn insert_tx_can_retrieve_full_tx_from_graph() {
|
|||||||
let _ = graph.insert_tx(tx.clone());
|
let _ = graph.insert_tx(tx.clone());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph.get_tx(tx.txid()),
|
graph.get_tx(tx.txid()),
|
||||||
Some(GraphedTx::from_tx(&tx, &BTreeSet::new()))
|
Some(TxInGraph::from_tx(&tx, &BTreeSet::new()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user