Skip to content

Commit

Permalink
feat(chain): add TxGraph::canonical_set method
Browse files Browse the repository at this point in the history
This is an O(n) algorithm to determine the canonical set of txids.
  • Loading branch information
evanlinjin committed Nov 18, 2024
1 parent 2fc554b commit 3bdb612
Showing 1 changed file with 129 additions and 0 deletions.
129 changes: 129 additions & 0 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,135 @@ impl<A: Anchor> TxGraph<A> {
.filter_map(Result::transpose)
}

/// Get a canonical set of txids.
pub fn canoncial_set<C: ChainOracle>(
&self,
chain: &C,
chain_tip: BlockId,
) -> Result<HashSet<Txid>, C::Error> {
let mut definitely_canonical = HashSet::<Txid>::new();
let mut definitely_not_canonical = HashSet::<Txid>::new();
let txid_by_last_seen = self
.last_seen
.iter()
.map(|(&txid, &last_seen)| (last_seen, txid))
.collect::<BTreeSet<_>>();
for (_, txid) in txid_by_last_seen {
self.canonicalize_by_traversing_up(
chain,
chain_tip,
&mut definitely_canonical,
&mut definitely_not_canonical,
txid,
)?;
}
Ok(definitely_canonical)
}

fn canonicalize_by_traversing_up<C: ChainOracle>(
&self,
chain: &C,
chain_tip: BlockId,
definitely_canonical: &mut HashSet<Txid>,
definitely_not_canonical: &mut HashSet<Txid>,
txid: Txid,
) -> Result<(), C::Error> {
type TxWithId = (Txid, Arc<Transaction>);
let tx = match self.get_tx(txid) {
Some(tx) => tx,
None => return Ok(()),
};
let maybe_canonical = TxAncestors::new_include_root(
self,
tx,
|_: usize, tx: Arc<Transaction>| -> Option<Result<TxWithId, C::Error>> {
let txid = tx.compute_txid();
match self.is_definitely_canonical(
chain,
chain_tip,
definitely_canonical,
definitely_not_canonical,
txid,
) {
Ok(None) => Some(Ok((txid, tx))),
Ok(Some(_)) => None,
Err(err) => Some(Err(err)),
}
},
)
.collect::<Result<Vec<_>, C::Error>>()?;
// TODO: Check if this is correct.
for (txid, tx) in maybe_canonical {
if !definitely_not_canonical.contains(&txid) {
self.mark_definitely_canonical(definitely_canonical, tx);
}
}
Ok(())
}

fn is_definitely_canonical<C: ChainOracle>(
&self,
chain: &C,
chain_tip: BlockId,
definitely_canonical: &mut HashSet<Txid>,
definitely_not_canonical: &mut HashSet<Txid>,
txid: Txid,
) -> Result<Option<bool>, C::Error> {
if definitely_canonical.contains(&txid) {
return Ok(Some(true));
}
if definitely_not_canonical.contains(&txid) {
return Ok(Some(false));
}
let tx = match self.get_tx(txid) {
Some(tx) => tx,
None => return Ok(None),
};
for anchor in self.anchors.get(&txid).unwrap_or(&self.empty_anchors) {
if chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? == Some(true) {
self.mark_definitely_canonical(definitely_canonical, tx);
return Ok(Some(true));
}
}
for (_, conflicting_txid) in self.direct_conflicts(&tx) {
if definitely_canonical.contains(&conflicting_txid) {
self.mark_definitely_not_canonical(definitely_not_canonical, txid);
return Ok(Some(false));
}
}
Ok(None)
}

fn mark_definitely_not_canonical(
&self,
definitely_not_canonical: &mut HashSet<Txid>,
txid: Txid,
) {
TxDescendants::new_include_root(self, txid, |_: usize, txid: Txid| -> Option<()> {
if definitely_not_canonical.insert(txid) {
Some(())
} else {
None
}
})
.for_each(|_| {})
}

fn mark_definitely_canonical(
&self,
definitely_canonical: &mut HashSet<Txid>,
tx: Arc<Transaction>,
) {
TxAncestors::new_include_root(self, tx, |_: usize, tx: Arc<Transaction>| -> Option<()> {
if definitely_canonical.insert(tx.compute_txid()) {
Some(())
} else {
None
}
})
.for_each(|_| {})
}

/// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
/// `chain_tip`.
///
Expand Down

0 comments on commit 3bdb612

Please sign in to comment.