use crate::collections::*;
use crate::spk_txout::SpkTxOutIndex;
use crate::BlockId;
use crate::CanonicalIter;
use crate::CanonicalReason;
use crate::CanonicalizationParams;
use crate::ObservedIn;
use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
use alloc::vec::Vec;
use bdk_core::ConfirmationBlockTime;
pub use bdk_core::TxUpdate;
use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
use core::fmt::{self, Formatter};
use core::ops::RangeBounds;
use core::{
convert::Infallible,
ops::{Deref, RangeInclusive},
};
impl<A: Ord> From<TxGraph<A>> for TxUpdate<A> {
fn from(graph: TxGraph<A>) -> Self {
let mut tx_update = TxUpdate::default();
tx_update.txs = graph.full_txs().map(|tx_node| tx_node.tx).collect();
tx_update.txouts = graph
.floating_txouts()
.map(|(op, txo)| (op, txo.clone()))
.collect();
tx_update.anchors = graph
.anchors
.into_iter()
.flat_map(|(txid, anchors)| anchors.into_iter().map(move |a| (a, txid)))
.collect();
tx_update.seen_ats = graph.last_seen.into_iter().collect();
tx_update.evicted_ats = graph.last_evicted.into_iter().collect();
tx_update
}
}
impl<A: Anchor> From<TxUpdate<A>> for TxGraph<A> {
fn from(update: TxUpdate<A>) -> Self {
let mut graph = TxGraph::<A>::default();
let _ = graph.apply_update(update);
graph
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TxGraph<A = ConfirmationBlockTime> {
txs: HashMap<Txid, TxNodeInternal>,
spends: BTreeMap<OutPoint, HashSet<Txid>>,
anchors: HashMap<Txid, BTreeSet<A>>,
first_seen: HashMap<Txid, u64>,
last_seen: HashMap<Txid, u64>,
last_evicted: HashMap<Txid, u64>,
txs_by_highest_conf_heights: BTreeSet<(u32, Txid)>,
txs_by_last_seen: BTreeSet<(u64, Txid)>,
empty_outspends: HashSet<Txid>,
empty_anchors: BTreeSet<A>,
}
impl<A> Default for TxGraph<A> {
fn default() -> Self {
Self {
txs: Default::default(),
spends: Default::default(),
anchors: Default::default(),
first_seen: Default::default(),
last_seen: Default::default(),
last_evicted: Default::default(),
txs_by_highest_conf_heights: Default::default(),
txs_by_last_seen: Default::default(),
empty_outspends: Default::default(),
empty_anchors: Default::default(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TxNode<'a, T, A> {
pub txid: Txid,
pub tx: T,
pub anchors: &'a BTreeSet<A>,
pub first_seen: Option<u64>,
pub last_seen: Option<u64>,
}
impl<T, A> Deref for TxNode<'_, T, A> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.tx
}
}
#[derive(Clone, Debug, PartialEq)]
enum TxNodeInternal {
Whole(Arc<Transaction>),
Partial(BTreeMap<u32, TxOut>),
}
impl Default for TxNodeInternal {
fn default() -> Self {
Self::Partial(BTreeMap::new())
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CanonicalTx<'a, T, A> {
pub chain_position: ChainPosition<A>,
pub tx_node: TxNode<'a, T, A>,
}
impl<'a, T, A> From<CanonicalTx<'a, T, A>> for Txid {
fn from(tx: CanonicalTx<'a, T, A>) -> Self {
tx.tx_node.txid
}
}
impl<'a, A> From<CanonicalTx<'a, Arc<Transaction>, A>> for Arc<Transaction> {
fn from(tx: CanonicalTx<'a, Arc<Transaction>, A>) -> Self {
tx.tx_node.tx
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum CalculateFeeError {
MissingTxOut(Vec<OutPoint>),
NegativeFee(SignedAmount),
}
impl fmt::Display for CalculateFeeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
CalculateFeeError::MissingTxOut(outpoints) => write!(
f,
"missing `TxOut` for one or more of the inputs of the tx: {outpoints:?}",
),
CalculateFeeError::NegativeFee(fee) => write!(
f,
"transaction is invalid according to the graph and has negative fee: {}",
fee.display_dynamic()
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CalculateFeeError {}
impl<A> TxGraph<A> {
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs.iter().flat_map(|(txid, tx)| match tx {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
.iter()
.enumerate()
.map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
.collect::<Vec<_>>(),
TxNodeInternal::Partial(txouts) => txouts
.iter()
.map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
.collect::<Vec<_>>(),
})
}
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()
}
pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
self.txs.iter().filter_map(|(&txid, tx)| match tx {
TxNodeInternal::Whole(tx) => Some(TxNode {
txid,
tx: tx.clone(),
anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors),
first_seen: self.first_seen.get(&txid).copied(),
last_seen: self.last_seen.get(&txid).copied(),
}),
TxNodeInternal::Partial(_) => None,
})
}
pub fn txs_with_no_anchor_or_last_seen(
&self,
) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
self.full_txs().filter_map(|tx| {
if tx.anchors.is_empty() && tx.last_seen.is_none() {
Some(tx)
} else {
None
}
})
}
pub fn get_tx(&self, txid: Txid) -> Option<Arc<Transaction>> {
self.get_tx_node(txid).map(|n| n.tx)
}
pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, A>> {
match &self.txs.get(&txid)? {
TxNodeInternal::Whole(tx) => Some(TxNode {
txid,
tx: tx.clone(),
anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors),
first_seen: self.first_seen.get(&txid).copied(),
last_seen: self.last_seen.get(&txid).copied(),
}),
_ => None,
}
}
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
match &self.txs.get(&outpoint.txid)? {
TxNodeInternal::Whole(tx) => tx.as_ref().output.get(outpoint.vout as usize),
TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
}
}
pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
Some(match &self.txs.get(&txid)? {
TxNodeInternal::Whole(tx) => tx
.as_ref()
.output
.iter()
.enumerate()
.map(|(vout, txout)| (vout as u32, txout))
.collect::<BTreeMap<_, _>>(),
TxNodeInternal::Partial(txouts) => txouts
.iter()
.map(|(vout, txout)| (*vout, txout))
.collect::<BTreeMap<_, _>>(),
})
}
pub fn get_last_evicted(&self, txid: Txid) -> Option<u64> {
self.last_evicted.get(&txid).copied()
}
pub fn calculate_fee(&self, tx: &Transaction) -> Result<Amount, CalculateFeeError> {
if tx.is_coinbase() {
return Ok(Amount::ZERO);
}
let (inputs_sum, missing_outputs) = tx.input.iter().fold(
(SignedAmount::ZERO, Vec::new()),
|(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
None => {
missing_outpoints.push(txin.previous_output);
(sum, missing_outpoints)
}
Some(txout) => {
sum += txout.value.to_signed().expect("valid `SignedAmount`");
(sum, missing_outpoints)
}
},
);
if !missing_outputs.is_empty() {
return Err(CalculateFeeError::MissingTxOut(missing_outputs));
}
let outputs_sum = tx
.output
.iter()
.map(|txout| txout.value.to_signed().expect("valid `SignedAmount`"))
.sum::<SignedAmount>();
let fee = inputs_sum - outputs_sum;
fee.to_unsigned()
.map_err(|_| CalculateFeeError::NegativeFee(fee))
}
pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
}
pub fn tx_spends(
&self,
txid: Txid,
) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
let start = OutPoint::new(txid, 0);
let end = OutPoint::new(txid, u32::MAX);
self.spends
.range(start..=end)
.map(|(outpoint, spends)| (outpoint.vout, spends))
}
}
impl<A: Clone + Ord> TxGraph<A> {
pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F, O>
where
T: Into<Arc<Transaction>>,
F: FnMut(usize, Arc<Transaction>) -> Option<O> + 'g,
{
TxAncestors::new_exclude_root(self, tx, walk_map)
}
pub fn walk_descendants<'g, F, O>(
&'g self,
txid: Txid,
walk_map: F,
) -> TxDescendants<'g, A, F, O>
where
F: FnMut(usize, Txid) -> Option<O> + 'g,
{
TxDescendants::new_exclude_root(self, txid, walk_map)
}
}
impl<A> TxGraph<A> {
pub fn walk_conflicts<'g, F, O>(
&'g self,
tx: &'g Transaction,
walk_map: F,
) -> TxDescendants<'g, A, F, O>
where
F: FnMut(usize, Txid) -> Option<O> + 'g,
{
let txids = self.direct_conflicts(tx).map(|(_, txid)| txid);
TxDescendants::from_multiple_include_root(self, txids, walk_map)
}
pub fn direct_conflicts<'g>(
&'g self,
tx: &'g Transaction,
) -> impl Iterator<Item = (usize, Txid)> + 'g {
let txid = tx.compute_txid();
tx.input
.iter()
.enumerate()
.filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin)))
.flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned()))
.filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
}
pub fn all_anchors(&self) -> &HashMap<Txid, BTreeSet<A>> {
&self.anchors
}
pub fn is_empty(&self) -> bool {
self.txs.is_empty()
}
}
impl<A: Anchor> TxGraph<A> {
pub fn map_anchors<A2: Anchor, F>(self, f: F) -> TxGraph<A2>
where
F: FnMut(A) -> A2,
{
let mut new_graph = TxGraph::<A2>::default();
new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
new_graph
}
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
let mut new = Self::default();
for tx in txs.into_iter() {
let _ = new.insert_tx(tx);
}
new
}
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
let mut changeset = ChangeSet::<A>::default();
let tx_node = self.txs.entry(outpoint.txid).or_default();
match tx_node {
TxNodeInternal::Whole(_) => {
}
TxNodeInternal::Partial(partial_tx) => {
match partial_tx.insert(outpoint.vout, txout.clone()) {
Some(old_txout) => {
debug_assert_eq!(
txout, old_txout,
"txout of the same outpoint should never change"
);
}
None => {
changeset.txouts.insert(outpoint, txout);
}
}
}
}
changeset
}
pub fn insert_tx<T: Into<Arc<Transaction>>>(&mut self, tx: T) -> ChangeSet<A> {
fn _merge_tx_witnesses(
original_tx: &Arc<Transaction>,
other_tx: &Arc<Transaction>,
) -> Option<Arc<Transaction>> {
debug_assert_eq!(
original_tx.input.len(),
other_tx.input.len(),
"tx input count must be the same"
);
let merged_input = Iterator::zip(original_tx.input.iter(), other_tx.input.iter())
.map(|(original_txin, other_txin)| {
let original_key = core::cmp::Reverse((
original_txin.witness.is_empty(),
original_txin.witness.size(),
&original_txin.witness,
));
let other_key = core::cmp::Reverse((
other_txin.witness.is_empty(),
other_txin.witness.size(),
&other_txin.witness,
));
if original_key > other_key {
original_txin.clone()
} else {
other_txin.clone()
}
})
.collect::<Vec<_>>();
if merged_input == original_tx.input {
return None;
}
if merged_input == other_tx.input {
return Some(other_tx.clone());
}
Some(Arc::new(Transaction {
input: merged_input,
..(**original_tx).clone()
}))
}
let tx: Arc<Transaction> = tx.into();
let txid = tx.compute_txid();
let mut changeset = ChangeSet::<A>::default();
let tx_node = self.txs.entry(txid).or_default();
match tx_node {
TxNodeInternal::Whole(existing_tx) => {
if existing_tx.as_ref() != tx.as_ref() {
if let Some(merged_tx) = _merge_tx_witnesses(existing_tx, &tx) {
*existing_tx = merged_tx.clone();
changeset.txs.insert(merged_tx);
}
}
}
partial_tx => {
for txin in &tx.input {
if txin.previous_output.is_null() {
continue;
}
self.spends
.entry(txin.previous_output)
.or_default()
.insert(txid);
}
*partial_tx = TxNodeInternal::Whole(tx.clone());
changeset.txs.insert(tx);
}
}
changeset
}
pub fn batch_insert_unconfirmed<T: Into<Arc<Transaction>>>(
&mut self,
txs: impl IntoIterator<Item = (T, u64)>,
) -> ChangeSet<A> {
let mut changeset = ChangeSet::<A>::default();
for (tx, seen_at) in txs {
let tx: Arc<Transaction> = tx.into();
changeset.merge(self.insert_seen_at(tx.compute_txid(), seen_at));
changeset.merge(self.insert_tx(tx));
}
changeset
}
pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
let mut old_top_h = None;
let mut new_top_h = anchor.confirmation_height_upper_bound();
let is_changed = match self.anchors.entry(txid) {
hash_map::Entry::Occupied(mut e) => {
old_top_h = e
.get()
.iter()
.last()
.map(Anchor::confirmation_height_upper_bound);
if let Some(old_top_h) = old_top_h {
if old_top_h > new_top_h {
new_top_h = old_top_h;
}
}
let is_changed = e.get_mut().insert(anchor.clone());
is_changed
}
hash_map::Entry::Vacant(e) => {
e.insert(core::iter::once(anchor.clone()).collect());
true
}
};
let mut changeset = ChangeSet::<A>::default();
if is_changed {
let new_top_is_changed = match old_top_h {
None => true,
Some(old_top_h) if old_top_h != new_top_h => true,
_ => false,
};
if new_top_is_changed {
if let Some(prev_top_h) = old_top_h {
self.txs_by_highest_conf_heights.remove(&(prev_top_h, txid));
}
self.txs_by_highest_conf_heights.insert((new_top_h, txid));
}
changeset.anchors.insert((anchor, txid));
}
changeset
}
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
let mut changeset_first_seen = self.update_first_seen(txid, seen_at);
let changeset_last_seen = self.update_last_seen(txid, seen_at);
changeset_first_seen.merge(changeset_last_seen);
changeset_first_seen
}
fn update_first_seen(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
let is_changed = match self.first_seen.entry(txid) {
hash_map::Entry::Occupied(mut e) => {
let first_seen = e.get_mut();
let change = *first_seen > seen_at;
if change {
*first_seen = seen_at;
}
change
}
hash_map::Entry::Vacant(e) => {
e.insert(seen_at);
true
}
};
let mut changeset = ChangeSet::<A>::default();
if is_changed {
changeset.first_seen.insert(txid, seen_at);
}
changeset
}
fn update_last_seen(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
let mut old_last_seen = None;
let is_changed = match self.last_seen.entry(txid) {
hash_map::Entry::Occupied(mut e) => {
let last_seen = e.get_mut();
old_last_seen = Some(*last_seen);
let change = *last_seen < seen_at;
if change {
*last_seen = seen_at;
}
change
}
hash_map::Entry::Vacant(e) => {
e.insert(seen_at);
true
}
};
let mut changeset = ChangeSet::<A>::default();
if is_changed {
if let Some(old_last_seen) = old_last_seen {
self.txs_by_last_seen.remove(&(old_last_seen, txid));
}
self.txs_by_last_seen.insert((seen_at, txid));
changeset.last_seen.insert(txid, seen_at);
}
changeset
}
pub fn insert_evicted_at(&mut self, txid: Txid, evicted_at: u64) -> ChangeSet<A> {
let is_changed = match self.last_evicted.entry(txid) {
hash_map::Entry::Occupied(mut e) => {
let last_evicted = e.get_mut();
let change = *last_evicted < evicted_at;
if change {
*last_evicted = evicted_at;
}
change
}
hash_map::Entry::Vacant(e) => {
e.insert(evicted_at);
true
}
};
let mut changeset = ChangeSet::<A>::default();
if is_changed {
changeset.last_evicted.insert(txid, evicted_at);
}
changeset
}
pub fn batch_insert_relevant_evicted_at(
&mut self,
evicted_ats: impl IntoIterator<Item = (Txid, u64)>,
) -> ChangeSet<A> {
let mut changeset = ChangeSet::default();
for (txid, evicted_at) in evicted_ats {
if self.txs.contains_key(&txid) {
changeset.merge(self.insert_evicted_at(txid, evicted_at));
}
}
changeset
}
pub fn apply_update(&mut self, update: TxUpdate<A>) -> ChangeSet<A> {
let mut changeset = ChangeSet::<A>::default();
for tx in update.txs {
changeset.merge(self.insert_tx(tx));
}
for (outpoint, txout) in update.txouts {
changeset.merge(self.insert_txout(outpoint, txout));
}
for (anchor, txid) in update.anchors {
changeset.merge(self.insert_anchor(txid, anchor));
}
for (txid, seen_at) in update.seen_ats {
changeset.merge(self.insert_seen_at(txid, seen_at));
}
for (txid, evicted_at) in update.evicted_ats {
changeset.merge(self.insert_evicted_at(txid, evicted_at));
}
changeset
}
pub fn initial_changeset(&self) -> ChangeSet<A> {
ChangeSet {
txs: self.full_txs().map(|tx_node| tx_node.tx).collect(),
txouts: self
.floating_txouts()
.map(|(op, txout)| (op, txout.clone()))
.collect(),
anchors: self
.anchors
.iter()
.flat_map(|(txid, anchors)| anchors.iter().map(|a| (a.clone(), *txid)))
.collect(),
first_seen: self.first_seen.iter().map(|(&k, &v)| (k, v)).collect(),
last_seen: self.last_seen.iter().map(|(&k, &v)| (k, v)).collect(),
last_evicted: self.last_evicted.iter().map(|(&k, &v)| (k, v)).collect(),
}
}
pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
for tx in changeset.txs {
let _ = self.insert_tx(tx);
}
for (outpoint, txout) in changeset.txouts {
let _ = self.insert_txout(outpoint, txout);
}
for (anchor, txid) in changeset.anchors {
let _ = self.insert_anchor(txid, anchor);
}
for (txid, seen_at) in changeset.last_seen {
let _ = self.insert_seen_at(txid, seen_at);
}
for (txid, evicted_at) in changeset.last_evicted {
let _ = self.insert_evicted_at(txid, evicted_at);
}
}
}
impl<A: Anchor> TxGraph<A> {
pub fn try_list_canonical_txs<'a, C: ChainOracle + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
fn find_direct_anchor<A: Anchor, C: ChainOracle>(
tx_node: &TxNode<'_, Arc<Transaction>, A>,
chain: &C,
chain_tip: BlockId,
) -> Result<Option<A>, C::Error> {
tx_node
.anchors
.iter()
.find_map(|a| -> Option<Result<A, C::Error>> {
match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
Ok(Some(true)) => Some(Ok(a.clone())),
Ok(Some(false)) | Ok(None) => None,
Err(err) => Some(Err(err)),
}
})
.transpose()
}
self.canonical_iter(chain, chain_tip, params)
.flat_map(move |res| {
res.map(|(txid, _, canonical_reason)| {
let tx_node = self.get_tx_node(txid).expect("must contain tx");
let chain_position = match canonical_reason {
CanonicalReason::Assumed { descendant } => match descendant {
Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
Some(anchor) => ChainPosition::Confirmed {
anchor,
transitively: None,
},
None => ChainPosition::Unconfirmed {
first_seen: tx_node.first_seen,
last_seen: tx_node.last_seen,
},
},
None => ChainPosition::Unconfirmed {
first_seen: tx_node.first_seen,
last_seen: tx_node.last_seen,
},
},
CanonicalReason::Anchor { anchor, descendant } => match descendant {
Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
Some(anchor) => ChainPosition::Confirmed {
anchor,
transitively: None,
},
None => ChainPosition::Confirmed {
anchor,
transitively: descendant,
},
},
None => ChainPosition::Confirmed {
anchor,
transitively: None,
},
},
CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
first_seen: tx_node.first_seen,
last_seen: Some(last_seen),
},
ObservedIn::Block(_) => ChainPosition::Unconfirmed {
first_seen: tx_node.first_seen,
last_seen: None,
},
},
};
Ok(CanonicalTx {
chain_position,
tx_node,
})
})
})
}
pub fn list_canonical_txs<'a, C: ChainOracle<Error = Infallible> + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
self.try_list_canonical_txs(chain, chain_tip, params)
.map(|res| res.expect("infallible"))
}
pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
let mut canon_txs = HashMap::<Txid, CanonicalTx<Arc<Transaction>, A>>::new();
let mut canon_spends = HashMap::<OutPoint, Txid>::new();
for r in self.try_list_canonical_txs(chain, chain_tip, params) {
let canonical_tx = r?;
let txid = canonical_tx.tx_node.txid;
if !canonical_tx.tx_node.tx.is_coinbase() {
for txin in &canonical_tx.tx_node.tx.input {
let _res = canon_spends.insert(txin.previous_output, txid);
assert!(_res.is_none(), "tried to replace {_res:?} with {txid:?}",);
}
}
canon_txs.insert(txid, canonical_tx);
}
Ok(outpoints.into_iter().filter_map(move |(spk_i, outpoint)| {
let canon_tx = canon_txs.get(&outpoint.txid)?;
let txout = canon_tx
.tx_node
.tx
.output
.get(outpoint.vout as usize)
.cloned()?;
let chain_position = canon_tx.chain_position.clone();
let spent_by = canon_spends.get(&outpoint).map(|spend_txid| {
let spend_tx = canon_txs
.get(spend_txid)
.cloned()
.expect("must be canonical");
(spend_tx.chain_position, *spend_txid)
});
let is_on_coinbase = canon_tx.tx_node.is_coinbase();
Some((
spk_i,
FullTxOut {
outpoint,
txout,
chain_position,
spent_by,
is_on_coinbase,
},
))
}))
}
pub fn txids_by_descending_anchor_height(
&self,
) -> impl ExactSizeIterator<Item = (u32, Txid)> + '_ {
self.txs_by_highest_conf_heights.iter().copied().rev()
}
pub fn txids_by_descending_last_seen(&self) -> impl Iterator<Item = (u64, Txid)> + '_ {
self.txs_by_last_seen
.iter()
.copied()
.rev()
.filter(|(last_seen, txid)| match self.last_evicted.get(txid) {
Some(last_evicted) => last_evicted < last_seen,
None => true,
})
}
pub fn canonical_iter<'a, C: ChainOracle>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
) -> CanonicalIter<'a, A, C> {
CanonicalIter::new(self, chain, chain_tip, params)
}
pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
self.try_filter_chain_txouts(chain, chain_tip, params, outpoints)
.expect("oracle is infallible")
}
pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
Ok(self
.try_filter_chain_txouts(chain, chain_tip, params, outpoints)?
.filter(|(_, full_txo)| full_txo.spent_by.is_none()))
}
pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
params: CanonicalizationParams,
txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
self.try_filter_chain_unspents(chain, chain_tip, params, txouts)
.expect("oracle is infallible")
}
pub fn try_balance<C: ChainOracle, OI: Clone>(
&self,
chain: &C,
chain_tip: BlockId,
params: CanonicalizationParams,
outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
mut trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
) -> Result<Balance, C::Error> {
let mut immature = Amount::ZERO;
let mut trusted_pending = Amount::ZERO;
let mut untrusted_pending = Amount::ZERO;
let mut confirmed = Amount::ZERO;
for (spk_i, txout) in self.try_filter_chain_unspents(chain, chain_tip, params, outpoints)? {
match &txout.chain_position {
ChainPosition::Confirmed { .. } => {
if txout.is_confirmed_and_spendable(chain_tip.height) {
confirmed += txout.txout.value;
} else if !txout.is_mature(chain_tip.height) {
immature += txout.txout.value;
}
}
ChainPosition::Unconfirmed { .. } => {
if trust_predicate(&spk_i, txout.txout.script_pubkey) {
trusted_pending += txout.txout.value;
} else {
untrusted_pending += txout.txout.value;
}
}
}
}
Ok(Balance {
immature,
trusted_pending,
untrusted_pending,
confirmed,
})
}
pub fn balance<C: ChainOracle<Error = Infallible>, OI: Clone>(
&self,
chain: &C,
chain_tip: BlockId,
params: CanonicalizationParams,
outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
) -> Balance {
self.try_balance(chain, chain_tip, params, outpoints, trust_predicate)
.expect("oracle is infallible")
}
pub fn try_list_expected_spk_txids<'a, C, I>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
indexer: &'a impl AsRef<SpkTxOutIndex<I>>,
spk_index_range: impl RangeBounds<I> + 'a,
) -> impl Iterator<Item = Result<(ScriptBuf, Txid), C::Error>> + 'a
where
C: ChainOracle,
I: fmt::Debug + Clone + Ord + 'a,
{
let indexer = indexer.as_ref();
self.try_list_canonical_txs(chain, chain_tip, CanonicalizationParams::default())
.flat_map(move |res| -> Vec<Result<(ScriptBuf, Txid), C::Error>> {
let range = &spk_index_range;
let c_tx = match res {
Ok(c_tx) => c_tx,
Err(err) => return vec![Err(err)],
};
let relevant_spks = indexer.relevant_spks_of_tx(&c_tx.tx_node);
relevant_spks
.into_iter()
.filter(|(i, _)| range.contains(i))
.map(|(_, spk)| Ok((spk, c_tx.tx_node.txid)))
.collect()
})
}
pub fn list_expected_spk_txids<'a, C, I>(
&'a self,
chain: &'a C,
chain_tip: BlockId,
indexer: &'a impl AsRef<SpkTxOutIndex<I>>,
spk_index_range: impl RangeBounds<I> + 'a,
) -> impl Iterator<Item = (ScriptBuf, Txid)> + 'a
where
C: ChainOracle<Error = Infallible>,
I: fmt::Debug + Clone + Ord + 'a,
{
self.try_list_expected_spk_txids(chain, chain_tip, indexer, spk_index_range)
.map(|r| r.expect("infallible"))
}
pub fn from_changeset(changeset: ChangeSet<A>) -> Self {
let mut graph = Self::default();
graph.apply_changeset(changeset);
graph
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "A: Ord + serde::Deserialize<'de>",
serialize = "A: Ord + serde::Serialize",
))
)]
#[must_use]
pub struct ChangeSet<A = ()> {
pub txs: BTreeSet<Arc<Transaction>>,
pub txouts: BTreeMap<OutPoint, TxOut>,
pub anchors: BTreeSet<(A, Txid)>,
pub last_seen: BTreeMap<Txid, u64>,
#[cfg_attr(feature = "serde", serde(default))]
pub last_evicted: BTreeMap<Txid, u64>,
#[cfg_attr(feature = "serde", serde(default))]
pub first_seen: BTreeMap<Txid, u64>,
}
impl<A> Default for ChangeSet<A> {
fn default() -> Self {
Self {
txs: Default::default(),
txouts: Default::default(),
anchors: Default::default(),
first_seen: Default::default(),
last_seen: Default::default(),
last_evicted: Default::default(),
}
}
}
impl<A> ChangeSet<A> {
pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
self.txs
.iter()
.flat_map(|tx| {
tx.output
.iter()
.enumerate()
.map(move |(vout, txout)| (OutPoint::new(tx.compute_txid(), vout as _), txout))
})
.chain(self.txouts.iter().map(|(op, txout)| (*op, txout)))
}
pub fn anchor_heights(&self) -> impl Iterator<Item = u32> + '_
where
A: Anchor,
{
let mut dedup = None;
self.anchors
.iter()
.map(|(a, _)| a.anchor_block().height)
.filter(move |height| {
let duplicate = dedup == Some(*height);
dedup = Some(*height);
!duplicate
})
}
}
impl<A: Ord> Merge for ChangeSet<A> {
fn merge(&mut self, other: Self) {
self.txs.extend(other.txs);
self.txouts.extend(other.txouts);
self.anchors.extend(other.anchors);
self.first_seen.extend(
other
.first_seen
.into_iter()
.filter(|(txid, update_fs)| match self.first_seen.get(txid) {
Some(existing) => update_fs < existing,
None => true,
})
.collect::<Vec<_>>(),
);
self.last_seen.extend(
other
.last_seen
.into_iter()
.filter(|(txid, update_ls)| self.last_seen.get(txid) < Some(update_ls))
.collect::<Vec<_>>(),
);
self.last_evicted.extend(
other
.last_evicted
.into_iter()
.filter(|(txid, update_lm)| self.last_evicted.get(txid) < Some(update_lm))
.collect::<Vec<_>>(),
);
}
fn is_empty(&self) -> bool {
self.txs.is_empty()
&& self.txouts.is_empty()
&& self.anchors.is_empty()
&& self.first_seen.is_empty()
&& self.last_seen.is_empty()
&& self.last_evicted.is_empty()
}
}
impl<A: Ord> ChangeSet<A> {
pub fn map_anchors<A2: Ord, F>(self, mut f: F) -> ChangeSet<A2>
where
F: FnMut(A) -> A2,
{
ChangeSet {
txs: self.txs,
txouts: self.txouts,
anchors: BTreeSet::<(A2, Txid)>::from_iter(
self.anchors.into_iter().map(|(a, txid)| (f(a), txid)),
),
first_seen: self.first_seen,
last_seen: self.last_seen,
last_evicted: self.last_evicted,
}
}
}
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
fn as_ref(&self) -> &TxGraph<A> {
self
}
}
pub struct TxAncestors<'g, A, F, O>
where
F: FnMut(usize, Arc<Transaction>) -> Option<O>,
{
graph: &'g TxGraph<A>,
visited: HashSet<Txid>,
queue: VecDeque<(usize, Arc<Transaction>)>,
filter_map: F,
}
impl<'g, A, F, O> TxAncestors<'g, A, F, O>
where
F: FnMut(usize, Arc<Transaction>) -> Option<O>,
{
pub(crate) fn new_include_root(
graph: &'g TxGraph<A>,
tx: impl Into<Arc<Transaction>>,
filter_map: F,
) -> Self {
Self {
graph,
visited: Default::default(),
queue: [(0, tx.into())].into(),
filter_map,
}
}
pub(crate) fn new_exclude_root(
graph: &'g TxGraph<A>,
tx: impl Into<Arc<Transaction>>,
filter_map: F,
) -> Self {
let mut ancestors = Self {
graph,
visited: Default::default(),
queue: Default::default(),
filter_map,
};
ancestors.populate_queue(1, tx.into());
ancestors
}
#[allow(unused)]
pub(crate) fn from_multiple_include_root<I>(
graph: &'g TxGraph<A>,
txs: I,
filter_map: F,
) -> Self
where
I: IntoIterator,
I::Item: Into<Arc<Transaction>>,
{
Self {
graph,
visited: Default::default(),
queue: txs.into_iter().map(|tx| (0, tx.into())).collect(),
filter_map,
}
}
#[allow(unused)]
pub(crate) fn from_multiple_exclude_root<I>(
graph: &'g TxGraph<A>,
txs: I,
filter_map: F,
) -> Self
where
I: IntoIterator,
I::Item: Into<Arc<Transaction>>,
{
let mut ancestors = Self {
graph,
visited: Default::default(),
queue: Default::default(),
filter_map,
};
for tx in txs {
ancestors.populate_queue(1, tx.into());
}
ancestors
}
pub fn run_until_finished(self) {
self.for_each(|_| {})
}
fn populate_queue(&mut self, depth: usize, tx: Arc<Transaction>) {
let ancestors = tx
.input
.iter()
.map(|txin| txin.previous_output.txid)
.filter(|&prev_txid| self.visited.insert(prev_txid))
.filter_map(|prev_txid| self.graph.get_tx(prev_txid))
.map(|tx| (depth, tx));
self.queue.extend(ancestors);
}
}
impl<A, F, O> Iterator for TxAncestors<'_, A, F, O>
where
F: FnMut(usize, Arc<Transaction>) -> Option<O>,
{
type Item = O;
fn next(&mut self) -> Option<Self::Item> {
loop {
let (ancestor_depth, tx) = self.queue.pop_front()?;
let item = match (self.filter_map)(ancestor_depth, tx.clone()) {
Some(item) => item,
None => continue,
};
self.populate_queue(ancestor_depth + 1, tx);
return Some(item);
}
}
}
pub struct TxDescendants<'g, A, F, O>
where
F: FnMut(usize, Txid) -> Option<O>,
{
graph: &'g TxGraph<A>,
visited: HashSet<Txid>,
queue: VecDeque<(usize, Txid)>,
filter_map: F,
}
impl<'g, A, F, O> TxDescendants<'g, A, F, O>
where
F: FnMut(usize, Txid) -> Option<O>,
{
#[allow(unused)]
pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
Self {
graph,
visited: Default::default(),
queue: [(0, txid)].into(),
filter_map,
}
}
pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
let mut descendants = Self {
graph,
visited: Default::default(),
queue: Default::default(),
filter_map,
};
descendants.populate_queue(1, txid);
descendants
}
pub(crate) fn from_multiple_include_root<I>(
graph: &'g TxGraph<A>,
txids: I,
filter_map: F,
) -> Self
where
I: IntoIterator<Item = Txid>,
{
Self {
graph,
visited: Default::default(),
queue: txids.into_iter().map(|txid| (0, txid)).collect(),
filter_map,
}
}
#[allow(unused)]
pub(crate) fn from_multiple_exclude_root<I>(
graph: &'g TxGraph<A>,
txids: I,
filter_map: F,
) -> Self
where
I: IntoIterator<Item = Txid>,
{
let mut descendants = Self {
graph,
visited: Default::default(),
queue: Default::default(),
filter_map,
};
for txid in txids {
descendants.populate_queue(1, txid);
}
descendants
}
pub fn run_until_finished(self) {
self.for_each(|_| {})
}
fn populate_queue(&mut self, depth: usize, txid: Txid) {
let spend_paths = self
.graph
.spends
.range(tx_outpoint_range(txid))
.flat_map(|(_, spends)| spends)
.map(|&txid| (depth, txid));
self.queue.extend(spend_paths);
}
}
impl<A, F, O> Iterator for TxDescendants<'_, A, F, O>
where
F: FnMut(usize, Txid) -> Option<O>,
{
type Item = O;
fn next(&mut self) -> Option<Self::Item> {
let (op_spends, txid, item) = loop {
let (op_spends, txid) = self.queue.pop_front()?;
if self.visited.insert(txid) {
if let Some(item) = (self.filter_map)(op_spends, txid) {
break (op_spends, txid, item);
}
}
};
self.populate_queue(op_spends + 1, txid);
Some(item)
}
}
fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
}