Skip to content

Commit 6239c8f

Browse files
committed
refactor(chain)!: TopologicalIter does not require TxGraph and
`ChainOracle` - optimize the `canonical_roots` building in `mark_canonical` to just lookup for the ancestors already calculated in the `canonical_ancestors`. - remove the dependency on `TxGraph` and `ChainOracle` for the `TopologicalIter`, expect a closure to use in `sort_by_key` instead.
1 parent 70ee41b commit 6239c8f

File tree

2 files changed

+93
-112
lines changed

2 files changed

+93
-112
lines changed

crates/chain/src/canonical_iter.rs

Lines changed: 45 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::collections::{HashMap, HashSet, VecDeque};
2-
use crate::tx_graph::{TxAncestors, TxDescendants, TxNode};
2+
use crate::tx_graph::{TxAncestors, TxDescendants};
33
use crate::{Anchor, ChainOracle, TxGraph};
44
use alloc::boxed::Box;
55
use alloc::collections::BTreeSet;
@@ -213,20 +213,17 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
213213
self.not_canonical.remove(&txid);
214214
}
215215
} else {
216-
// TODO: (@oleonardolima) Can this be optimized somehow ?
217-
// Can we just do a simple lookup on the `canonical_ancestors` field ?
218216
for txid in staged_queue {
219217
let tx = self.tx_graph.get_tx(txid).expect("tx must exist");
220-
let ancestors = tx
221-
.input
222-
.iter()
223-
.map(|txin| txin.previous_output.txid)
224-
.filter_map(|prev_txid| self.tx_graph.get_tx(prev_txid))
225-
.collect::<Vec<_>>();
226-
227-
// check if it's a root: it's either a coinbase transaction or has not known
228-
// ancestors in the tx_graph
229-
if tx.is_coinbase() || ancestors.is_empty() {
218+
let has_no_ancestors = self
219+
.canonical_ancestors
220+
.get(&txid)
221+
.expect("should exist")
222+
.is_empty();
223+
224+
// check if it's a root: it's either a coinbase transaction or has no known
225+
// ancestors in the tx_graph.
226+
if tx.is_coinbase() || has_no_ancestors {
230227
self.canonical_roots.push_back(txid);
231228
}
232229
}
@@ -271,12 +268,17 @@ impl<A: Anchor, C: ChainOracle> Iterator for CanonicalIter<'_, A, C> {
271268
}
272269

273270
if !self.canonical_roots.is_empty() {
274-
let topological_iter = TopologicalIteratorWithLevels::new(
275-
self.tx_graph,
276-
self.chain,
277-
self.chain_tip,
271+
let topological_iter = TopologicalIter::new(
278272
&self.canonical_ancestors,
279273
self.canonical_roots.drain(..).collect(),
274+
|txid| {
275+
let tx_node = self.tx_graph.get_tx_node(txid).expect("tx should exist");
276+
self.tx_graph
277+
.find_direct_anchor(&tx_node, self.chain, self.chain_tip)
278+
.expect("should not fail")
279+
.map(|anchor| anchor.confirmation_height_upper_bound())
280+
.unwrap_or(u32::MAX) // FIXME: (@oleonardo) should we use the `first_seen` instead ?
281+
},
280282
);
281283
self.queue.extend(topological_iter);
282284
}
@@ -383,97 +385,65 @@ impl<A: Clone> CanonicalReason<A> {
383385
}
384386
}
385387

386-
struct TopologicalIteratorWithLevels<'a, A, C> {
387-
tx_graph: &'a TxGraph<A>,
388-
chain: &'a C,
389-
chain_tip: BlockId,
390-
388+
struct TopologicalIter<F>
389+
where
390+
F: FnMut(Txid) -> u32,
391+
{
391392
current_level: Vec<Txid>,
392393
next_level: Vec<Txid>,
393394

394395
adj_list: HashMap<Txid, Vec<Txid>>,
395-
parent_count: HashMap<Txid, usize>,
396+
inputs_count: HashMap<Txid, usize>,
396397

397398
current_index: usize,
399+
400+
sort_by: F,
398401
}
399402

400-
impl<'a, A: Anchor, C: ChainOracle> TopologicalIteratorWithLevels<'a, A, C> {
401-
fn new(
402-
tx_graph: &'a TxGraph<A>,
403-
chain: &'a C,
404-
chain_tip: BlockId,
405-
ancestors_by_txid: &HashMap<Txid, Vec<Txid>>,
406-
roots: Vec<Txid>,
407-
) -> Self {
408-
let mut parent_count = HashMap::new();
403+
impl<F> TopologicalIter<F>
404+
where
405+
F: FnMut(bitcoin::Txid) -> u32,
406+
{
407+
fn new(ancestors: &HashMap<Txid, Vec<Txid>>, roots: Vec<Txid>, mut sort_by: F) -> Self {
408+
let mut inputs_count = HashMap::new();
409409
let mut adj_list: HashMap<Txid, Vec<Txid>> = HashMap::new();
410410

411-
for (txid, ancestors) in ancestors_by_txid {
411+
for (txid, ancestors) in ancestors {
412412
for ancestor in ancestors {
413413
adj_list.entry(*ancestor).or_default().push(*txid);
414-
*parent_count.entry(*txid).or_insert(0) += 1;
414+
*inputs_count.entry(*txid).or_insert(0) += 1;
415415
}
416416
}
417417

418418
let mut current_level: Vec<Txid> = roots.to_vec();
419419

420-
// Sort the initial level by confirmation height
421-
current_level.sort_by_key(|&txid| {
422-
let tx_node = tx_graph.get_tx_node(txid).expect("tx should exist");
423-
Self::find_direct_anchor(&tx_node, chain, chain_tip)
424-
.expect("should not fail")
425-
.map(|anchor| anchor.confirmation_height_upper_bound())
426-
.unwrap_or(u32::MAX)
427-
});
420+
// sort the level by confirmation height
421+
current_level.sort_by_key(|txid| sort_by(*txid));
428422

429423
Self {
430424
current_level,
431425
next_level: Vec::new(),
432426
adj_list,
433-
parent_count,
427+
inputs_count,
434428
current_index: 0,
435-
tx_graph,
436-
chain,
437-
chain_tip,
429+
sort_by,
438430
}
439431
}
440432

441-
fn find_direct_anchor(
442-
tx_node: &TxNode<'_, Arc<Transaction>, A>,
443-
chain: &C,
444-
chain_tip: BlockId,
445-
) -> Result<Option<A>, C::Error> {
446-
tx_node
447-
.anchors
448-
.iter()
449-
.find_map(|a| -> Option<Result<A, C::Error>> {
450-
match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
451-
Ok(Some(true)) => Some(Ok(a.clone())),
452-
Ok(Some(false)) | Ok(None) => None,
453-
Err(err) => Some(Err(err)),
454-
}
455-
})
456-
.transpose()
457-
}
458-
459433
fn advance_to_next_level(&mut self) {
460434
self.current_level = core::mem::take(&mut self.next_level);
461435

462-
// Sort by confirmation height
463-
self.current_level.sort_by_key(|&txid| {
464-
let tx_node = self.tx_graph.get_tx_node(txid).expect("tx should exist");
465-
466-
Self::find_direct_anchor(&tx_node, self.chain, self.chain_tip)
467-
.expect("should not fail")
468-
.map(|anchor| anchor.confirmation_height_upper_bound())
469-
.unwrap_or(u32::MAX)
470-
});
436+
// sort the level by confirmation height
437+
self.current_level.sort_by_key(|&txid| (self.sort_by)(txid));
471438

472439
self.current_index = 0;
473440
}
474441
}
475442

476-
impl<'a, A: Anchor, C: ChainOracle> Iterator for TopologicalIteratorWithLevels<'a, A, C> {
443+
impl<F> Iterator for TopologicalIter<F>
444+
where
445+
F: FnMut(bitcoin::Txid) -> u32,
446+
{
477447
type Item = Txid;
478448

479449
fn next(&mut self) -> Option<Self::Item> {
@@ -494,7 +464,7 @@ impl<'a, A: Anchor, C: ChainOracle> Iterator for TopologicalIteratorWithLevels<'
494464
for &tx in &self.current_level {
495465
if let Some(dependents) = self.adj_list.get(&tx) {
496466
for &dependent in dependents {
497-
if let Some(degree) = self.parent_count.get_mut(&dependent) {
467+
if let Some(degree) = self.inputs_count.get_mut(&dependent) {
498468
*degree -= 1;
499469
if *degree == 0 {
500470
self.next_level.push(dependent);

crates/chain/src/tx_graph.rs

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,30 @@ impl<A: Anchor> TxGraph<A> {
980980
}
981981

982982
impl<A: Anchor> TxGraph<A> {
983+
/// TODO: (@oleonardolima).
984+
///
985+
/// # Errors
986+
///
987+
/// This function will return an error if .
988+
pub(crate) fn find_direct_anchor<C: ChainOracle>(
989+
&self,
990+
tx_node: &TxNode<'_, Arc<Transaction>, A>,
991+
chain: &C,
992+
chain_tip: BlockId,
993+
) -> Result<Option<A>, C::Error> {
994+
tx_node
995+
.anchors
996+
.iter()
997+
.find_map(|a| -> Option<Result<A, C::Error>> {
998+
match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
999+
Ok(Some(true)) => Some(Ok(a.clone())),
1000+
Ok(Some(false)) | Ok(None) => None,
1001+
Err(err) => Some(Err(err)),
1002+
}
1003+
})
1004+
.transpose()
1005+
}
1006+
9831007
/// List graph transactions that are in `chain` with `chain_tip`.
9841008
///
9851009
/// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is
@@ -999,55 +1023,42 @@ impl<A: Anchor> TxGraph<A> {
9991023
chain_tip: BlockId,
10001024
params: CanonicalizationParams,
10011025
) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
1002-
fn find_direct_anchor<A: Anchor, C: ChainOracle>(
1003-
tx_node: &TxNode<'_, Arc<Transaction>, A>,
1004-
chain: &C,
1005-
chain_tip: BlockId,
1006-
) -> Result<Option<A>, C::Error> {
1007-
tx_node
1008-
.anchors
1009-
.iter()
1010-
.find_map(|a| -> Option<Result<A, C::Error>> {
1011-
match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
1012-
Ok(Some(true)) => Some(Ok(a.clone())),
1013-
Ok(Some(false)) | Ok(None) => None,
1014-
Err(err) => Some(Err(err)),
1015-
}
1016-
})
1017-
.transpose()
1018-
}
10191026
self.canonical_iter(chain, chain_tip, params)
10201027
.flat_map(move |res| {
10211028
res.map(|(txid, _, canonical_reason)| {
10221029
let tx_node = self.get_tx_node(txid).expect("must contain tx");
10231030
let chain_position = match canonical_reason {
10241031
CanonicalReason::Assumed { descendant } => match descendant {
1025-
Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
1026-
Some(anchor) => ChainPosition::Confirmed {
1027-
anchor,
1028-
transitively: None,
1029-
},
1030-
None => ChainPosition::Unconfirmed {
1031-
first_seen: tx_node.first_seen,
1032-
last_seen: tx_node.last_seen,
1033-
},
1034-
},
1032+
Some(_) => {
1033+
match self.find_direct_anchor(&tx_node, chain, chain_tip)? {
1034+
Some(anchor) => ChainPosition::Confirmed {
1035+
anchor,
1036+
transitively: None,
1037+
},
1038+
None => ChainPosition::Unconfirmed {
1039+
first_seen: tx_node.first_seen,
1040+
last_seen: tx_node.last_seen,
1041+
},
1042+
}
1043+
}
10351044
None => ChainPosition::Unconfirmed {
10361045
first_seen: tx_node.first_seen,
10371046
last_seen: tx_node.last_seen,
10381047
},
10391048
},
10401049
CanonicalReason::Anchor { anchor, descendant } => match descendant {
1041-
Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
1042-
Some(anchor) => ChainPosition::Confirmed {
1043-
anchor,
1044-
transitively: None,
1045-
},
1046-
None => ChainPosition::Confirmed {
1047-
anchor,
1048-
transitively: descendant,
1049-
},
1050-
},
1050+
Some(_) => {
1051+
match self.find_direct_anchor(&tx_node, chain, chain_tip)? {
1052+
Some(anchor) => ChainPosition::Confirmed {
1053+
anchor,
1054+
transitively: None,
1055+
},
1056+
None => ChainPosition::Confirmed {
1057+
anchor,
1058+
transitively: descendant,
1059+
},
1060+
}
1061+
}
10511062
None => ChainPosition::Confirmed {
10521063
anchor,
10531064
transitively: None,

0 commit comments

Comments
 (0)