Skip to content

Commit 66a4c81

Browse files
committed
feat(chain)!: TxGraph contain anchors in one field
Previously, the field `TxGraph::anchors` existed because we assumed there was use in having a partially-chronological order of transactions there. Turns out it wasn't used at all. This commit removes anchors from the `txs` field and reworks `anchors` field to be a map of `txid -> set<anchors>`. This is a breaking change since the signature of `all_anchors()` is changed.
1 parent b220d61 commit 66a4c81

File tree

2 files changed

+40
-37
lines changed

2 files changed

+40
-37
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,19 @@ use core::{
106106
ops::{Deref, RangeInclusive},
107107
};
108108

109-
impl<A> From<TxGraph<A>> for TxUpdate<A> {
109+
impl<A: Ord> From<TxGraph<A>> for TxUpdate<A> {
110110
fn from(graph: TxGraph<A>) -> Self {
111111
Self {
112112
txs: graph.full_txs().map(|tx_node| tx_node.tx).collect(),
113113
txouts: graph
114114
.floating_txouts()
115115
.map(|(op, txo)| (op, txo.clone()))
116116
.collect(),
117-
anchors: graph.anchors,
117+
anchors: graph
118+
.anchors
119+
.into_iter()
120+
.flat_map(|(txid, anchors)| anchors.into_iter().map(move |a| (a, txid)))
121+
.collect(),
118122
seen_ats: graph.last_seen.into_iter().collect(),
119123
}
120124
}
@@ -135,15 +139,15 @@ impl<A: Ord + Clone> From<TxUpdate<A>> for TxGraph<A> {
135139
/// [module-level documentation]: crate::tx_graph
136140
#[derive(Clone, Debug, PartialEq)]
137141
pub struct TxGraph<A = ()> {
138-
// all transactions that the graph is aware of in format: `(tx_node, tx_anchors)`
139-
txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>)>,
142+
txs: HashMap<Txid, TxNodeInternal>,
140143
spends: BTreeMap<OutPoint, HashSet<Txid>>,
141-
anchors: BTreeSet<(A, Txid)>,
144+
anchors: HashMap<Txid, BTreeSet<A>>,
142145
last_seen: HashMap<Txid, u64>,
143146

144147
// This atrocity exists so that `TxGraph::outspends()` can return a reference.
145148
// FIXME: This can be removed once `HashSet::new` is a const fn.
146149
empty_outspends: HashSet<Txid>,
150+
empty_anchors: BTreeSet<A>,
147151
}
148152

149153
impl<A> Default for TxGraph<A> {
@@ -154,6 +158,7 @@ impl<A> Default for TxGraph<A> {
154158
anchors: Default::default(),
155159
last_seen: Default::default(),
156160
empty_outspends: Default::default(),
161+
empty_anchors: Default::default(),
157162
}
158163
}
159164
}
@@ -238,7 +243,7 @@ impl<A> TxGraph<A> {
238243
///
239244
/// This includes txouts of both full transactions as well as floating transactions.
240245
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
241-
self.txs.iter().flat_map(|(txid, (tx, _))| match tx {
246+
self.txs.iter().flat_map(|(txid, tx)| match tx {
242247
TxNodeInternal::Whole(tx) => tx
243248
.as_ref()
244249
.output
@@ -260,7 +265,7 @@ impl<A> TxGraph<A> {
260265
pub fn floating_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
261266
self.txs
262267
.iter()
263-
.filter_map(|(txid, (tx_node, _))| match tx_node {
268+
.filter_map(|(txid, tx_node)| match tx_node {
264269
TxNodeInternal::Whole(_) => None,
265270
TxNodeInternal::Partial(txouts) => Some(
266271
txouts
@@ -273,17 +278,15 @@ impl<A> TxGraph<A> {
273278

274279
/// Iterate over all full transactions in the graph.
275280
pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
276-
self.txs
277-
.iter()
278-
.filter_map(|(&txid, (tx, anchors))| match tx {
279-
TxNodeInternal::Whole(tx) => Some(TxNode {
280-
txid,
281-
tx: tx.clone(),
282-
anchors,
283-
last_seen_unconfirmed: self.last_seen.get(&txid).copied(),
284-
}),
285-
TxNodeInternal::Partial(_) => None,
286-
})
281+
self.txs.iter().filter_map(|(&txid, tx)| match tx {
282+
TxNodeInternal::Whole(tx) => Some(TxNode {
283+
txid,
284+
tx: tx.clone(),
285+
anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors),
286+
last_seen_unconfirmed: self.last_seen.get(&txid).copied(),
287+
}),
288+
TxNodeInternal::Partial(_) => None,
289+
})
287290
}
288291

289292
/// Iterate over graph transactions with no anchors or last-seen.
@@ -311,10 +314,10 @@ impl<A> TxGraph<A> {
311314
/// Get a transaction node by txid. This only returns `Some` for full transactions.
312315
pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, A>> {
313316
match &self.txs.get(&txid)? {
314-
(TxNodeInternal::Whole(tx), anchors) => Some(TxNode {
317+
TxNodeInternal::Whole(tx) => Some(TxNode {
315318
txid,
316319
tx: tx.clone(),
317-
anchors,
320+
anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors),
318321
last_seen_unconfirmed: self.last_seen.get(&txid).copied(),
319322
}),
320323
_ => None,
@@ -323,7 +326,7 @@ impl<A> TxGraph<A> {
323326

324327
/// Obtains a single tx output (if any) at the specified outpoint.
325328
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
326-
match &self.txs.get(&outpoint.txid)?.0 {
329+
match &self.txs.get(&outpoint.txid)? {
327330
TxNodeInternal::Whole(tx) => tx.as_ref().output.get(outpoint.vout as usize),
328331
TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
329332
}
@@ -333,7 +336,7 @@ impl<A> TxGraph<A> {
333336
///
334337
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
335338
pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
336-
Some(match &self.txs.get(&txid)?.0 {
339+
Some(match &self.txs.get(&txid)? {
337340
TxNodeInternal::Whole(tx) => tx
338341
.as_ref()
339342
.output
@@ -496,7 +499,7 @@ impl<A> TxGraph<A> {
496499
}
497500

498501
/// Get all transaction anchors known by [`TxGraph`].
499-
pub fn all_anchors(&self) -> &BTreeSet<(A, Txid)> {
502+
pub fn all_anchors(&self) -> &HashMap<Txid, BTreeSet<A>> {
500503
&self.anchors
501504
}
502505

@@ -540,7 +543,7 @@ impl<A: Clone + Ord> TxGraph<A> {
540543
/// [`apply_changeset`]: Self::apply_changeset
541544
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
542545
let mut changeset = ChangeSet::<A>::default();
543-
let (tx_node, _) = self.txs.entry(outpoint.txid).or_default();
546+
let tx_node = self.txs.entry(outpoint.txid).or_default();
544547
match tx_node {
545548
TxNodeInternal::Whole(_) => {
546549
// ignore this txout we have the full one already.
@@ -573,7 +576,7 @@ impl<A: Clone + Ord> TxGraph<A> {
573576
let txid = tx.compute_txid();
574577
let mut changeset = ChangeSet::<A>::default();
575578

576-
let (tx_node, _) = self.txs.entry(txid).or_default();
579+
let tx_node = self.txs.entry(txid).or_default();
577580
match tx_node {
578581
TxNodeInternal::Whole(existing_tx) => {
579582
debug_assert_eq!(
@@ -625,13 +628,7 @@ impl<A: Clone + Ord> TxGraph<A> {
625628
/// `anchor`.
626629
pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
627630
let mut changeset = ChangeSet::<A>::default();
628-
if self.anchors.insert((anchor.clone(), txid)) {
629-
let (_tx_node, anchors) = self.txs.entry(txid).or_default();
630-
let _inserted = anchors.insert(anchor.clone());
631-
debug_assert!(
632-
_inserted,
633-
"anchors in `.anchors` and `.txs` should be consistent"
634-
);
631+
if self.anchors.entry(txid).or_default().insert(anchor.clone()) {
635632
changeset.anchors.insert((anchor, txid));
636633
}
637634
changeset
@@ -711,7 +708,11 @@ impl<A: Clone + Ord> TxGraph<A> {
711708
.floating_txouts()
712709
.map(|(op, txout)| (op, txout.clone()))
713710
.collect(),
714-
anchors: self.anchors.clone(),
711+
anchors: self
712+
.anchors
713+
.iter()
714+
.flat_map(|(txid, anchors)| anchors.iter().map(|a| (a.clone(), *txid)))
715+
.collect(),
715716
last_seen: self.last_seen.iter().map(|(&k, &v)| (k, v)).collect(),
716717
}
717718
}
@@ -763,12 +764,12 @@ impl<A: Anchor> TxGraph<A> {
763764
chain_tip: BlockId,
764765
txid: Txid,
765766
) -> Result<Option<ChainPosition<&A>>, C::Error> {
766-
let (tx_node, anchors) = match self.txs.get(&txid) {
767+
let tx_node = match self.txs.get(&txid) {
767768
Some(v) => v,
768769
None => return Ok(None),
769770
};
770771

771-
for anchor in anchors {
772+
for anchor in self.anchors.get(&txid).unwrap_or(&self.empty_anchors) {
772773
match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
773774
Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))),
774775
_ => continue,

crates/chain/tests/test_tx_graph.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,11 +1122,13 @@ fn call_map_anchors_with_non_deterministic_anchor() {
11221122
}
11231123
assert!(new_txs.next().is_none());
11241124

1125-
let new_graph_anchors: Vec<_> = new_graph
1125+
let mut new_graph_anchors: Vec<_> = new_graph
11261126
.all_anchors()
11271127
.iter()
1128-
.map(|i| i.0.anchor_block)
1128+
.flat_map(|(_, anchors)| anchors)
1129+
.map(|a| a.anchor_block)
11291130
.collect();
1131+
new_graph_anchors.sort();
11301132
assert_eq!(
11311133
new_graph_anchors,
11321134
vec![

0 commit comments

Comments
 (0)