use bdk_chain::{
chain_graph,
keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
- sparse_chain,
- tx_graph::TxInGraph,
- BlockId, ConfirmationTime,
+ sparse_chain, BlockId, ConfirmationTime,
};
use bitcoin::consensus::encode::serialize;
use bitcoin::secp256k1::Secp256k1;
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
Some(TransactionDetails {
- transaction: if include_raw {
- Some(tx.tx.clone())
- } else {
- None
- },
+ transaction: if include_raw { Some(tx.clone()) } else { None },
txid,
received,
sent,
/// unconfirmed transactions last.
pub fn transactions(
&self,
- ) -> impl DoubleEndedIterator<Item = (ConfirmationTime, TxInGraph<'_, Transaction, ()>)> + '_
- {
+ ) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
self.keychain_tracker
.chain_graph()
.transactions_in_chain()
Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
return Err(Error::TransactionConfirmed)
}
- Some((_, tx)) => tx.tx.clone(),
+ Some((_, tx)) => tx.clone(),
};
if !tx
outpoint: txin.previous_output,
psbt_input: Box::new(psbt::Input {
witness_utxo: Some(txout.clone()),
- non_witness_utxo: Some(prev_tx.tx.clone()),
+ non_witness_utxo: Some(prev_tx.clone()),
..Default::default()
}),
},
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
}
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
- psbt_input.non_witness_utxo = Some(prev_tx.tx.clone());
+ psbt_input.non_witness_utxo = Some(prev_tx.clone());
}
}
Ok(psbt_input)
use crate::{
collections::HashSet,
sparse_chain::{self, ChainPosition, SparseChain},
- tx_graph::{self, TxGraph, TxInGraph},
+ tx_graph::{self, TxGraph},
BlockId, ForEachTxOut, FullTxOut, TxHeight,
};
use alloc::{string::ToString, vec::Vec};
pub fn new(chain: SparseChain<P>, graph: TxGraph) -> Result<Self, NewError<P>> {
let mut missing = HashSet::default();
for (pos, txid) in chain.txids() {
- if let Some(graphed_tx) = graph.get_tx(*txid) {
+ if let Some(tx) = graph.get_tx(*txid) {
let conflict = graph
- .walk_conflicts(graphed_tx.tx, |_, txid| {
- Some((chain.tx_position(txid)?.clone(), txid))
- })
+ .walk_conflicts(tx, |_, txid| Some((chain.tx_position(txid)?.clone(), txid)))
.next();
if let Some((conflict_pos, conflict)) = conflict {
return Err(NewError::Conflict {
match self.chain.tx_position(*txid) {
Some(original_pos) => {
if original_pos != pos {
- let graphed_tx = self
+ let tx = self
.graph
.get_tx(*txid)
.expect("tx must exist as it is referenced in sparsechain")
let _ = inflated_chain
.insert_tx(*txid, pos.clone())
.expect("must insert since this was already in update");
- let _ = inflated_graph.insert_tx(graphed_tx.tx.clone());
+ let _ = inflated_graph.insert_tx(tx.clone());
}
}
None => {
///
/// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in
/// the unconfirmed transaction list within the [`SparseChain`].
- pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, TxInGraph<'_, Transaction, ()>)> {
+ pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> {
let position = self.chain.tx_position(txid)?;
- let graphed_tx = self.graph.get_tx(txid).expect("must exist");
- Some((position, graphed_tx))
+ let tx = self.graph.get_tx(txid).expect("must exist");
+ Some((position, tx))
}
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
None => continue,
};
- let mut full_tx = self.graph.get_tx(txid).map(|tx| tx.tx);
+ let mut full_tx = self.graph.get_tx(txid);
if full_tx.is_none() {
full_tx = changeset.graph.tx.iter().find(|tx| tx.txid() == txid)
/// Iterate over the full transactions and their position in the chain ordered by their position
/// in ascending order.
- pub fn transactions_in_chain(
- &self,
- ) -> impl DoubleEndedIterator<Item = (&P, TxInGraph<'_, Transaction, ()>)> {
+ pub fn transactions_in_chain(&self) -> impl DoubleEndedIterator<Item = (&P, &Transaction)> {
self.chain
.txids()
.map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
use crate::{
keychain::Balance,
sparse_chain::ChainPosition,
- tx_graph::{Additions, TxGraph, TxInGraph},
+ tx_graph::{Additions, TxGraph, TxNode},
BlockAnchor, ChainOracle, FullTxOut, ObservedIn, TxIndex, TxIndexAdditions,
};
/// Where the transaction is observed (in a block or in mempool).
pub observed_in: ObservedIn<&'a A>,
/// The transaction with anchors and last seen timestamp.
- pub tx: TxInGraph<'a, T, A>,
+ pub tx: TxNode<'a, T, A>,
}
/// An outwards-facing view of a relevant txout that is part of the *best chain*'s history.
#[derive(Clone, Debug, PartialEq)]
pub struct TxGraph<A = ()> {
// all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
- txs: HashMap<Txid, (TxNode, BTreeSet<A>, u64)>,
+ txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>, u64)>,
spends: BTreeMap<OutPoint, HashSet<Txid>>,
anchors: BTreeSet<(A, Txid)>,
// 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 node that resides in a [`TxGraph`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct TxInGraph<'a, T, A> {
+pub struct TxNode<'a, T, A> {
/// Txid of the transaction.
pub txid: Txid,
/// A partial or full representation of the transaction.
pub last_seen: u64,
}
-impl<'a, T, A> Deref for TxInGraph<'a, T, A> {
+impl<'a, T, A> Deref for TxNode<'a, T, A> {
type Target = T;
fn deref(&self) -> &Self::Target {
}
}
-impl<'a, A> TxInGraph<'a, Transaction, A> {
+impl<'a, A> TxNode<'a, Transaction, A> {
pub fn from_tx(tx: &'a Transaction, anchors: &'a BTreeSet<A>) -> Self {
Self {
txid: tx.txid(),
/// This can either be a whole transaction, or a partial transaction (where we only have select
/// outputs).
#[derive(Clone, Debug, PartialEq)]
-enum TxNode {
+enum TxNodeInternal {
Whole(Transaction),
Partial(BTreeMap<u32, TxOut>),
}
-impl Default for TxNode {
+impl Default for TxNodeInternal {
fn default() -> Self {
Self::Partial(BTreeMap::new())
}
/// Iterate over all tx outputs known by [`TxGraph`].
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
- TxNode::Whole(tx) => tx
+ TxNodeInternal::Whole(tx) => tx
.output
.iter()
.enumerate()
.map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
.collect::<Vec<_>>(),
- TxNode::Partial(txouts) => txouts
+ TxNodeInternal::Partial(txouts) => txouts
.iter()
.map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
.collect::<Vec<_>>(),
}
/// Iterate over all full transactions in the graph.
- pub fn full_transactions(&self) -> impl Iterator<Item = TxInGraph<'_, Transaction, A>> {
+ pub fn full_transactions(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
self.txs
.iter()
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
- TxNode::Whole(tx) => Some(TxInGraph {
+ TxNodeInternal::Whole(tx) => Some(TxNode {
txid,
tx,
anchors,
last_seen: *last_seen,
}),
- TxNode::Partial(_) => None,
+ TxNodeInternal::Partial(_) => None,
})
}
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
///
/// [`get_txout`]: Self::get_txout
- pub fn get_tx(&self, txid: Txid) -> Option<TxInGraph<'_, Transaction, A>> {
+ pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> {
+ self.get_tx_node(txid).map(|n| n.tx)
+ }
+
+ /// Get a transaction node by txid. This only returns `Some` for full transactions.
+ pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Transaction, A>> {
match &self.txs.get(&txid)? {
- (TxNode::Whole(tx), anchors, last_seen) => Some(TxInGraph {
+ (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
txid,
tx,
anchors,
/// Obtains a single tx output (if any) at the specified outpoint.
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
match &self.txs.get(&outpoint.txid)?.0 {
- TxNode::Whole(tx) => tx.output.get(outpoint.vout as usize),
- TxNode::Partial(txouts) => txouts.get(&outpoint.vout),
+ TxNodeInternal::Whole(tx) => tx.output.get(outpoint.vout as usize),
+ TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
}
}
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
pub fn txouts(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
Some(match &self.txs.get(&txid)?.0 {
- TxNode::Whole(tx) => tx
+ TxNodeInternal::Whole(tx) => tx
.output
.iter()
.enumerate()
.map(|(vout, txout)| (vout as u32, txout))
.collect::<BTreeMap<_, _>>(),
- TxNode::Partial(txouts) => txouts
+ TxNodeInternal::Partial(txouts) => txouts
.iter()
.map(|(vout, txout)| (*vout, txout))
.collect::<BTreeMap<_, _>>(),
/// Iterate over all partial transactions (outputs only) in the graph.
pub fn partial_transactions(
&self,
- ) -> impl Iterator<Item = TxInGraph<'_, BTreeMap<u32, TxOut>, A>> {
+ ) -> impl Iterator<Item = TxNode<'_, BTreeMap<u32, TxOut>, A>> {
self.txs
.iter()
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
- TxNode::Whole(_) => None,
- TxNode::Partial(partial) => Some(TxInGraph {
+ TxNodeInternal::Whole(_) => None,
+ TxNodeInternal::Partial(partial) => Some(TxNode {
txid,
tx: partial,
anchors,
update.txs.insert(
outpoint.txid,
(
- TxNode::Partial([(outpoint.vout, txout)].into()),
+ TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
BTreeSet::new(),
0,
),
let mut update = Self::default();
update
.txs
- .insert(tx.txid(), (TxNode::Whole(tx), BTreeSet::new(), 0));
+ .insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
self.determine_additions(&update)
}
});
match self.txs.get_mut(&txid) {
- Some((tx_node @ TxNode::Partial(_), _, _)) => {
- *tx_node = TxNode::Whole(tx);
+ Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
+ *tx_node = TxNodeInternal::Whole(tx);
}
- Some((TxNode::Whole(tx), _, _)) => {
+ Some((TxNodeInternal::Whole(tx), _, _)) => {
debug_assert_eq!(
tx.txid(),
txid,
}
None => {
self.txs
- .insert(txid, (TxNode::Whole(tx), BTreeSet::new(), 0));
+ .insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
}
}
}
.or_insert_with(Default::default);
match tx_entry {
- (TxNode::Whole(_), _, _) => { /* do nothing since we already have full tx */ }
- (TxNode::Partial(txouts), _, _) => {
+ (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
+ }
+ (TxNodeInternal::Partial(txouts), _, _) => {
txouts.insert(outpoint.vout, txout);
}
}
for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
- (None, TxNode::Whole(update_tx)) => {
+ (None, TxNodeInternal::Whole(update_tx)) => {
additions.tx.insert(update_tx.clone());
0
}
- (None, TxNode::Partial(update_txos)) => {
+ (None, TxNodeInternal::Partial(update_txos)) => {
additions.txout.extend(
update_txos
.iter()
);
0
}
- (Some((TxNode::Whole(_), _, last_seen)), _) => *last_seen,
- (Some((TxNode::Partial(_), _, last_seen)), TxNode::Whole(update_tx)) => {
+ (Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen,
+ (
+ Some((TxNodeInternal::Partial(_), _, last_seen)),
+ TxNodeInternal::Whole(update_tx),
+ ) => {
additions.tx.insert(update_tx.clone());
*last_seen
}
- (Some((TxNode::Partial(txos), _, last_seen)), TxNode::Partial(update_txos)) => {
+ (
+ Some((TxNodeInternal::Partial(txos), _, last_seen)),
+ TxNodeInternal::Partial(update_txos),
+ ) => {
additions.txout.extend(
update_txos
.iter()
// The tx is not anchored to a block which is in the best chain, let's check whether we can
// ignore it by checking conflicts!
let tx = match tx_node {
- TxNode::Whole(tx) => tx,
- TxNode::Partial(_) => {
+ TxNodeInternal::Whole(tx) => tx,
+ TxNodeInternal::Partial(_) => {
// [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 :/
return Ok(None);
// [TODO] Is this logic correct? I do not think so, but it should be good enough for now!
let mut latest_last_seen = 0_u64;
- for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx(txid)) {
+ for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) {
for block_id in conflicting_tx.anchors.iter().map(A::anchor_block) {
if chain.is_block_in_best_chain(block_id)? {
// conflicting tx is in best chain, so the current tx cannot be in best chain!
#[macro_use]
mod common;
-use std::collections::BTreeSet;
-
use bdk_chain::{
chain_graph::*,
collections::HashSet,
sparse_chain,
- tx_graph::{self, TxGraph, TxInGraph},
+ tx_graph::{self, TxGraph},
BlockId, TxHeight,
};
use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness};
let _ = cg.insert_tx(tx.clone(), TxHeight::Unconfirmed).unwrap();
assert_eq!(
cg.get_tx_in_chain(tx.txid()),
- Some((
- &TxHeight::Unconfirmed,
- TxInGraph {
- txid: tx.txid(),
- tx: &tx,
- anchors: &BTreeSet::new(),
- last_seen: 0
- }
- ))
+ Some((&TxHeight::Unconfirmed, &tx,))
);
}
assert_eq!(
cg.transactions_in_chain().collect::<Vec<_>>(),
vec![
- (
- &TxHeight::Confirmed(0),
- TxInGraph::from_tx(&txs[2], &BTreeSet::new())
- ),
- (
- &TxHeight::Confirmed(1),
- TxInGraph::from_tx(&txs[0], &BTreeSet::new())
- ),
- (
- &TxHeight::Unconfirmed,
- TxInGraph::from_tx(&txs[1], &BTreeSet::new())
- ),
+ (&TxHeight::Confirmed(0), &txs[2],),
+ (&TxHeight::Confirmed(1), &txs[0],),
+ (&TxHeight::Unconfirmed, &txs[1],),
]
);
}
#![cfg(feature = "miniscript")]
#[macro_use]
mod common;
-use std::collections::BTreeSet;
use bdk_chain::{
keychain::{Balance, KeychainTracker},
bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut},
Descriptor,
},
- tx_graph::TxInGraph,
BlockId, ConfirmationTime, TxHeight,
};
use bitcoin::TxIn;
.chain_graph()
.transactions_in_chain()
.collect::<Vec<_>>(),
- vec![(
- &ConfirmationTime::Unconfirmed,
- TxInGraph::from_tx(&tx, &BTreeSet::new())
- )]
+ vec![(&ConfirmationTime::Unconfirmed, &tx,)]
);
assert_eq!(
mod common;
use bdk_chain::{
collections::*,
- tx_graph::{Additions, TxGraph, TxInGraph},
- BlockId,
-};
-use bitcoin::{
- hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
+ tx_graph::{Additions, TxGraph},
};
+use bitcoin::{hashes::Hash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid};
use core::iter;
#[test]
)];
let mut graph = {
- let mut graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut graph = TxGraph::<()>::default();
for (outpoint, txout) in &original_ops {
assert_eq!(
graph.insert_txout(*outpoint, txout.clone()),
output: vec![],
};
- let mut graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut graph = TxGraph::<()>::default();
let _ = graph.insert_tx(tx);
assert!(graph.outspends(OutPoint::null()).is_empty());
assert!(graph.tx_outspends(Txid::all_zeros()).next().is_none());
output: vec![],
};
- let mut graph1 = TxGraph::<(u32, BlockHash)>::default();
- let mut graph2 = TxGraph::<(u32, BlockHash)>::default();
+ let mut graph1 = TxGraph::<()>::default();
+ let mut graph2 = TxGraph::<()>::default();
// insert in different order
let _ = graph1.insert_tx(tx1.clone());
output: vec![TxOut::default()],
};
- let mut graph = TxGraph::<BlockId>::default();
+ let mut graph = TxGraph::<()>::default();
let _ = graph.insert_tx(tx.clone());
- assert_eq!(
- graph.get_tx(tx.txid()),
- Some(TxInGraph::from_tx(&tx, &BTreeSet::new()))
- );
+ assert_eq!(graph.get_tx(tx.txid()), Some(&tx));
}
#[test]
fn insert_tx_displaces_txouts() {
- let mut tx_graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut tx_graph = TxGraph::<()>::default();
let tx = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
#[test]
fn insert_txout_does_not_displace_tx() {
- let mut tx_graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut tx_graph = TxGraph::<()>::default();
let tx = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
#[test]
fn test_calculate_fee() {
- let mut graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut graph = TxGraph::<()>::default();
let intx1 = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
output: vec![TxOut::default()],
};
- let graph = TxGraph::<(u32, BlockHash)>::default();
+ let graph = TxGraph::<()>::default();
assert_eq!(graph.calculate_fee(&tx), Some(0));
}
let txid_a = tx_a.txid();
let txid_b = tx_b.txid();
- let mut graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut graph = TxGraph::<()>::default();
let _ = graph.insert_tx(tx_a);
let _ = graph.insert_tx(tx_b);
})
.collect::<Vec<_>>();
- let mut graph = TxGraph::<(u32, BlockHash)>::default();
+ let mut graph = TxGraph::<()>::default();
let mut expected_txids = BTreeSet::new();
// these are NOT descendants of `tx_a`