Skip to content

Commit f348e6d

Browse files
committed
feat(chain): add TxGraph::canonical_set method
This is an O(n) algorithm to determine the canonical set of txids.
1 parent 66a4c81 commit f348e6d

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,135 @@ impl<A: Anchor> TxGraph<A> {
10541054
.filter_map(Result::transpose)
10551055
}
10561056

1057+
/// Get a canonical set of txids.
1058+
pub fn canoncial_set<C: ChainOracle>(
1059+
&self,
1060+
chain: &C,
1061+
chain_tip: BlockId,
1062+
) -> Result<HashSet<Txid>, C::Error> {
1063+
let mut definitely_canonical = HashSet::<Txid>::new();
1064+
let mut definitely_not_canonical = HashSet::<Txid>::new();
1065+
let txid_by_last_seen = self
1066+
.last_seen
1067+
.iter()
1068+
.map(|(&txid, &last_seen)| (last_seen, txid))
1069+
.collect::<BTreeSet<_>>();
1070+
for (_, txid) in txid_by_last_seen {
1071+
self.canonicalize_by_traversing_up(
1072+
chain,
1073+
chain_tip,
1074+
&mut definitely_canonical,
1075+
&mut definitely_not_canonical,
1076+
txid,
1077+
)?;
1078+
}
1079+
Ok(definitely_canonical)
1080+
}
1081+
1082+
fn canonicalize_by_traversing_up<C: ChainOracle>(
1083+
&self,
1084+
chain: &C,
1085+
chain_tip: BlockId,
1086+
definitely_canonical: &mut HashSet<Txid>,
1087+
definitely_not_canonical: &mut HashSet<Txid>,
1088+
txid: Txid,
1089+
) -> Result<(), C::Error> {
1090+
type TxWithId = (Txid, Arc<Transaction>);
1091+
let tx = match self.get_tx(txid) {
1092+
Some(tx) => tx,
1093+
None => return Ok(()),
1094+
};
1095+
let maybe_canonical = TxAncestors::new_include_root(
1096+
self,
1097+
tx,
1098+
|_: usize, tx: Arc<Transaction>| -> Option<Result<TxWithId, C::Error>> {
1099+
let txid = tx.compute_txid();
1100+
match self.is_definitely_canonical(
1101+
chain,
1102+
chain_tip,
1103+
definitely_canonical,
1104+
definitely_not_canonical,
1105+
txid,
1106+
) {
1107+
Ok(None) => Some(Ok((txid, tx))),
1108+
Ok(Some(_)) => None,
1109+
Err(err) => Some(Err(err)),
1110+
}
1111+
},
1112+
)
1113+
.collect::<Result<Vec<_>, C::Error>>()?;
1114+
// TODO: Check if this is correct.
1115+
for (txid, tx) in maybe_canonical {
1116+
if !definitely_not_canonical.contains(&txid) {
1117+
self.mark_definitely_canonical(definitely_canonical, tx);
1118+
}
1119+
}
1120+
Ok(())
1121+
}
1122+
1123+
fn is_definitely_canonical<C: ChainOracle>(
1124+
&self,
1125+
chain: &C,
1126+
chain_tip: BlockId,
1127+
definitely_canonical: &mut HashSet<Txid>,
1128+
definitely_not_canonical: &mut HashSet<Txid>,
1129+
txid: Txid,
1130+
) -> Result<Option<bool>, C::Error> {
1131+
if definitely_canonical.contains(&txid) {
1132+
return Ok(Some(true));
1133+
}
1134+
if definitely_not_canonical.contains(&txid) {
1135+
return Ok(Some(false));
1136+
}
1137+
let tx = match self.get_tx(txid) {
1138+
Some(tx) => tx,
1139+
None => return Ok(None),
1140+
};
1141+
for anchor in self.anchors.get(&txid).unwrap_or(&self.empty_anchors) {
1142+
if chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? == Some(true) {
1143+
self.mark_definitely_canonical(definitely_canonical, tx);
1144+
return Ok(Some(true));
1145+
}
1146+
}
1147+
for (_, conflicting_txid) in self.direct_conflicts(&tx) {
1148+
if definitely_canonical.contains(&conflicting_txid) {
1149+
self.mark_definitely_not_canonical(definitely_not_canonical, txid);
1150+
return Ok(Some(false));
1151+
}
1152+
}
1153+
Ok(None)
1154+
}
1155+
1156+
fn mark_definitely_not_canonical(
1157+
&self,
1158+
definitely_not_canonical: &mut HashSet<Txid>,
1159+
txid: Txid,
1160+
) {
1161+
TxDescendants::new_include_root(self, txid, |_: usize, txid: Txid| -> Option<()> {
1162+
if definitely_not_canonical.insert(txid) {
1163+
Some(())
1164+
} else {
1165+
None
1166+
}
1167+
})
1168+
.for_each(|_| {})
1169+
}
1170+
1171+
fn mark_definitely_canonical(
1172+
&self,
1173+
definitely_canonical: &mut HashSet<Txid>,
1174+
tx: Arc<Transaction>,
1175+
) {
1176+
TxAncestors::new_include_root(self, tx, |_: usize, tx: Arc<Transaction>| -> Option<()> {
1177+
if definitely_canonical.insert(tx.compute_txid()) {
1178+
Some(())
1179+
} else {
1180+
None
1181+
}
1182+
})
1183+
.for_each(|_| {})
1184+
}
1185+
10571186
/// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
10581187
/// `chain_tip`.
10591188
///

0 commit comments

Comments
 (0)