Skip to content

Commit abdf572

Browse files
committed
feat(chain): Add extract_descendant_subgraph() to CanonicalView
These methods are intended to help implement RBF logic. `CanonicalView::roots` is also added.
1 parent b45ecb9 commit abdf572

File tree

1 file changed

+89
-2
lines changed

1 file changed

+89
-2
lines changed

crates/chain/src/canonical_view.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//! }
2222
//! ```
2323
24-
use crate::collections::HashMap;
24+
use crate::collections::{BTreeSet, HashMap};
2525
use alloc::sync::Arc;
2626
use core::{fmt, ops::RangeBounds};
2727

@@ -81,7 +81,7 @@ impl<A: Ord> PartialOrd for CanonicalTx<A> {
8181
#[derive(Debug)]
8282
pub struct CanonicalView<A> {
8383
/// Ordered list of transaction IDs in in topological-spending order.
84-
order: Vec<Txid>,
84+
order: Vec<Txid>, // TODO: Not ordered - waiting on #2038
8585
/// Map of transaction IDs to their transaction data and chain position.
8686
txs: HashMap<Txid, (Arc<Transaction>, ChainPosition<A>)>,
8787
/// Map of outpoints to the transaction ID that spends them.
@@ -448,4 +448,91 @@ impl<A: Anchor> CanonicalView<A> {
448448
.collect()
449449
})
450450
}
451+
452+
/// Extracts a subgraph containing the specified transactions and all their descendants.
453+
///
454+
/// Takes transaction IDs and returns a new `CanonicalView` containing those transactions
455+
/// plus any transactions that spend their outputs (recursively). The extracted transactions
456+
/// are removed from this view.
457+
pub fn extract_descendant_subgraph(&mut self, txids: impl IntoIterator<Item = Txid>) -> Self {
458+
use crate::collections::hash_map::Entry;
459+
460+
let mut to_visit = txids.into_iter().collect::<Vec<Txid>>();
461+
462+
let mut spends = HashMap::<OutPoint, Txid>::new();
463+
let mut txs = HashMap::<Txid, (Arc<Transaction>, ChainPosition<A>)>::new();
464+
465+
while let Some(txid) = to_visit.pop() {
466+
let tx_entry = match txs.entry(txid) {
467+
Entry::Occupied(_) => continue, // Already visited.
468+
Entry::Vacant(entry) => entry,
469+
};
470+
471+
let (tx, pos) = match self.txs.remove(&txid) {
472+
Some(tx_and_pos) => tx_and_pos,
473+
None => continue, // Doesn't exist in graph.
474+
};
475+
476+
tx_entry.insert((tx.clone(), pos.clone()));
477+
478+
for op in (0_u32..tx.output.len() as u32).map(|vout| OutPoint::new(txid, vout)) {
479+
let spent_by = match self.spends.remove(&op) {
480+
Some(spent_by) => spent_by,
481+
None => continue,
482+
};
483+
spends.insert(op, spent_by);
484+
to_visit.push(spent_by);
485+
}
486+
}
487+
488+
// final pass to clean `self.spends`.
489+
for txin in txs.values().flat_map(|(tx, _)| &tx.input) {
490+
self.spends.remove(&txin.previous_output);
491+
}
492+
493+
// Remove extracted transactions from self.order
494+
let extracted_txids = txs.keys().copied().collect::<BTreeSet<Txid>>();
495+
self.order.retain(|txid| !extracted_txids.contains(txid));
496+
497+
// TODO: Use this with `TopologicalIter` once #2027 gets merged to return a view that has
498+
// topologically-ordered transactions.
499+
let _roots = txs
500+
.iter()
501+
.filter(|(_, (tx, _))| {
502+
tx.is_coinbase()
503+
|| tx
504+
.input
505+
.iter()
506+
.all(|txin| !spends.contains_key(&txin.previous_output))
507+
})
508+
.map(|(&txid, _)| txid);
509+
510+
CanonicalView {
511+
order: txs.keys().copied().collect(), // TODO: Not ordered.
512+
txs,
513+
spends,
514+
tip: self.tip,
515+
}
516+
}
517+
518+
/// Returns transactions without parent transactions in this view.
519+
///
520+
/// Root transactions are either coinbase transactions or transactions whose inputs
521+
/// reference outputs not present in this canonical view.
522+
pub fn roots(&self) -> impl Iterator<Item = CanonicalTx<A>> + '_ {
523+
self.txs
524+
.iter()
525+
.filter(|(_, (tx, _))| {
526+
tx.is_coinbase()
527+
|| tx
528+
.input
529+
.iter()
530+
.all(|txin| !self.spends.contains_key(&txin.previous_output))
531+
})
532+
.map(|(&txid, (tx, pos))| CanonicalTx {
533+
pos: pos.clone(),
534+
txid,
535+
tx: tx.clone(),
536+
})
537+
}
451538
}

0 commit comments

Comments
 (0)