|
21 | 21 | //! } |
22 | 22 | //! ``` |
23 | 23 |
|
24 | | -use crate::collections::HashMap; |
| 24 | +use crate::collections::{BTreeSet, HashMap}; |
25 | 25 | use alloc::sync::Arc; |
26 | 26 | use core::{fmt, ops::RangeBounds}; |
27 | 27 |
|
@@ -81,7 +81,7 @@ impl<A: Ord> PartialOrd for CanonicalTx<A> { |
81 | 81 | #[derive(Debug)] |
82 | 82 | pub struct CanonicalView<A> { |
83 | 83 | /// Ordered list of transaction IDs in in topological-spending order. |
84 | | - order: Vec<Txid>, |
| 84 | + order: Vec<Txid>, // TODO: Not ordered - waiting on #2038 |
85 | 85 | /// Map of transaction IDs to their transaction data and chain position. |
86 | 86 | txs: HashMap<Txid, (Arc<Transaction>, ChainPosition<A>)>, |
87 | 87 | /// Map of outpoints to the transaction ID that spends them. |
@@ -448,4 +448,91 @@ impl<A: Anchor> CanonicalView<A> { |
448 | 448 | .collect() |
449 | 449 | }) |
450 | 450 | } |
| 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 | + } |
451 | 538 | } |
0 commit comments