Skip to content

Commit 71a3e0e

Browse files
committed
feat(chain): get rid of TxGraph::determine_changeset
Contain most of the insertion logic in `.insert_{}` methods, thus simplifying `.apply_{}` methods. We can also get rid of `.determine_changeset`.
1 parent 6008897 commit 71a3e0e

File tree

2 files changed

+112
-167
lines changed

2 files changed

+112
-167
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 100 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -512,28 +512,66 @@ impl<A: Clone + Ord> TxGraph<A> {
512512
///
513513
/// [`apply_changeset`]: Self::apply_changeset
514514
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
515-
let mut update = Self::default();
516-
update.txs.insert(
517-
outpoint.txid,
518-
(
519-
TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
520-
BTreeSet::new(),
521-
),
522-
);
523-
self.apply_update(update)
515+
let mut changeset = ChangeSet::<A>::default();
516+
let (tx_node, _) = self.txs.entry(outpoint.txid).or_default();
517+
match tx_node {
518+
TxNodeInternal::Whole(_) => {
519+
// ignore this txout we have the full one already.
520+
// NOTE: You might think putting a debug_assert! here to check the output being
521+
// replaced was actually correct is a good idea but the tests have already been
522+
// written assuming this never panics.
523+
}
524+
TxNodeInternal::Partial(partial_tx) => {
525+
match partial_tx.insert(outpoint.vout, txout.clone()) {
526+
Some(old_txout) => {
527+
debug_assert_eq!(
528+
txout, old_txout,
529+
"txout of the same outpoint should never change"
530+
);
531+
}
532+
None => {
533+
changeset.txouts.insert(outpoint, txout);
534+
}
535+
}
536+
}
537+
}
538+
changeset
524539
}
525540

526541
/// Inserts the given transaction into [`TxGraph`].
527542
///
528543
/// The [`ChangeSet`] returned will be empty if `tx` already exists.
529544
pub fn insert_tx<T: Into<Arc<Transaction>>>(&mut self, tx: T) -> ChangeSet<A> {
530-
let tx = tx.into();
531-
let mut update = Self::default();
532-
update.txs.insert(
533-
tx.compute_txid(),
534-
(TxNodeInternal::Whole(tx), BTreeSet::new()),
535-
);
536-
self.apply_update(update)
545+
let tx: Arc<Transaction> = tx.into();
546+
let txid = tx.compute_txid();
547+
let mut changeset = ChangeSet::<A>::default();
548+
549+
let (tx_node, _) = self.txs.entry(txid).or_default();
550+
match tx_node {
551+
TxNodeInternal::Whole(existing_tx) => {
552+
debug_assert_eq!(
553+
existing_tx.as_ref(),
554+
tx.as_ref(),
555+
"tx of same txid should never change"
556+
);
557+
}
558+
partial_tx => {
559+
for txin in &tx.input {
560+
// this means the tx is coinbase so there is no previous output
561+
if txin.previous_output.is_null() {
562+
continue;
563+
}
564+
self.spends
565+
.entry(txin.previous_output)
566+
.or_default()
567+
.insert(txid);
568+
}
569+
*partial_tx = TxNodeInternal::Whole(tx.clone());
570+
changeset.txs.insert(tx);
571+
}
572+
}
573+
574+
changeset
537575
}
538576

539577
/// Batch insert unconfirmed transactions.
@@ -558,9 +596,17 @@ impl<A: Clone + Ord> TxGraph<A> {
558596
/// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in
559597
/// `anchor`.
560598
pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
561-
let mut update = Self::default();
562-
update.anchors.insert((anchor, txid));
563-
self.apply_update(update)
599+
let mut changeset = ChangeSet::<A>::default();
600+
if self.anchors.insert((anchor.clone(), txid)) {
601+
let (_tx_node, anchors) = self.txs.entry(txid).or_default();
602+
let _inserted = anchors.insert(anchor.clone());
603+
debug_assert!(
604+
_inserted,
605+
"anchors in `.anchors` and `.txs` should be consistent"
606+
);
607+
changeset.anchors.insert((anchor, txid));
608+
}
609+
changeset
564610
}
565611

566612
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
@@ -571,9 +617,13 @@ impl<A: Clone + Ord> TxGraph<A> {
571617
///
572618
/// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
573619
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
574-
let mut update = Self::default();
575-
update.last_seen.insert(txid, seen_at);
576-
self.apply_update(update)
620+
let mut changeset = ChangeSet::<A>::default();
621+
let last_seen = self.last_seen.entry(txid).or_default();
622+
if seen_at > *last_seen {
623+
*last_seen = seen_at;
624+
changeset.last_seen.insert(txid, seen_at);
625+
}
626+
changeset
577627
}
578628

579629
/// Update the last seen time for all unconfirmed transactions.
@@ -641,124 +691,50 @@ impl<A: Clone + Ord> TxGraph<A> {
641691
/// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
642692
/// exist in `update` but not in `self`).
643693
pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A> {
644-
let changeset = self.determine_changeset(update);
645-
self.apply_changeset(changeset.clone());
694+
let mut changeset = ChangeSet::<A>::default();
695+
for tx_node in update.full_txs() {
696+
changeset.merge(self.insert_tx(tx_node.tx));
697+
}
698+
for (outpoint, txout) in update.floating_txouts() {
699+
changeset.merge(self.insert_txout(outpoint, txout.clone()));
700+
}
701+
for (anchor, txid) in &update.anchors {
702+
changeset.merge(self.insert_anchor(*txid, anchor.clone()));
703+
}
704+
for (&txid, &last_seen) in &update.last_seen {
705+
changeset.merge(self.insert_seen_at(txid, last_seen));
706+
}
646707
changeset
647708
}
648709

649710
/// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`].
650711
pub fn initial_changeset(&self) -> ChangeSet<A> {
651-
Self::default().determine_changeset(self.clone())
712+
ChangeSet {
713+
txs: self.full_txs().map(|tx_node| tx_node.tx).collect(),
714+
txouts: self
715+
.floating_txouts()
716+
.map(|(op, txout)| (op, txout.clone()))
717+
.collect(),
718+
anchors: self.anchors.clone(),
719+
last_seen: self.last_seen.iter().map(|(&k, &v)| (k, v)).collect(),
720+
}
652721
}
653722

654723
/// Applies [`ChangeSet`] to [`TxGraph`].
655724
pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
656-
for wrapped_tx in changeset.txs {
657-
let tx = wrapped_tx.as_ref();
658-
let txid = tx.compute_txid();
659-
660-
tx.input
661-
.iter()
662-
.map(|txin| txin.previous_output)
663-
// coinbase spends are not to be counted
664-
.filter(|outpoint| !outpoint.is_null())
665-
// record spend as this tx has spent this outpoint
666-
.for_each(|outpoint| {
667-
self.spends.entry(outpoint).or_default().insert(txid);
668-
});
669-
670-
match self.txs.get_mut(&txid) {
671-
Some((tx_node @ TxNodeInternal::Partial(_), _)) => {
672-
*tx_node = TxNodeInternal::Whole(wrapped_tx.clone());
673-
}
674-
Some((TxNodeInternal::Whole(tx), _)) => {
675-
debug_assert_eq!(
676-
tx.as_ref().compute_txid(),
677-
txid,
678-
"tx should produce txid that is same as key"
679-
);
680-
}
681-
None => {
682-
self.txs
683-
.insert(txid, (TxNodeInternal::Whole(wrapped_tx), BTreeSet::new()));
684-
}
685-
}
725+
for tx in changeset.txs {
726+
let _ = self.insert_tx(tx);
686727
}
687-
688728
for (outpoint, txout) in changeset.txouts {
689-
let tx_entry = self.txs.entry(outpoint.txid).or_default();
690-
691-
match tx_entry {
692-
(TxNodeInternal::Whole(_), _) => { /* do nothing since we already have full tx */ }
693-
(TxNodeInternal::Partial(txouts), _) => {
694-
txouts.insert(outpoint.vout, txout);
695-
}
696-
}
729+
let _ = self.insert_txout(outpoint, txout);
697730
}
698-
699731
for (anchor, txid) in changeset.anchors {
700-
if self.anchors.insert((anchor.clone(), txid)) {
701-
let (_, anchors) = self.txs.entry(txid).or_default();
702-
anchors.insert(anchor);
703-
}
732+
let _ = self.insert_anchor(txid, anchor);
704733
}
705-
706-
for (txid, new_last_seen) in changeset.last_seen {
707-
let last_seen = self.last_seen.entry(txid).or_default();
708-
if new_last_seen > *last_seen {
709-
*last_seen = new_last_seen;
710-
}
734+
for (txid, seen_at) in changeset.last_seen {
735+
let _ = self.insert_seen_at(txid, seen_at);
711736
}
712737
}
713-
714-
/// Previews the resultant [`ChangeSet`] when [`Self`] is updated against the `update` graph.
715-
///
716-
/// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that
717-
/// exist in `update` but not in `self`).
718-
pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> {
719-
let mut changeset = ChangeSet::<A>::default();
720-
721-
for (&txid, (update_tx_node, _)) in &update.txs {
722-
match (self.txs.get(&txid), update_tx_node) {
723-
(None, TxNodeInternal::Whole(update_tx)) => {
724-
changeset.txs.insert(update_tx.clone());
725-
}
726-
(None, TxNodeInternal::Partial(update_txos)) => {
727-
changeset.txouts.extend(
728-
update_txos
729-
.iter()
730-
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
731-
);
732-
}
733-
(Some((TxNodeInternal::Whole(_), _)), _) => {}
734-
(Some((TxNodeInternal::Partial(_), _)), TxNodeInternal::Whole(update_tx)) => {
735-
changeset.txs.insert(update_tx.clone());
736-
}
737-
(
738-
Some((TxNodeInternal::Partial(txos), _)),
739-
TxNodeInternal::Partial(update_txos),
740-
) => {
741-
changeset.txouts.extend(
742-
update_txos
743-
.iter()
744-
.filter(|(vout, _)| !txos.contains_key(*vout))
745-
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
746-
);
747-
}
748-
}
749-
}
750-
751-
for (txid, update_last_seen) in update.last_seen {
752-
let prev_last_seen = self.last_seen.get(&txid).copied();
753-
if Some(update_last_seen) > prev_last_seen {
754-
changeset.last_seen.insert(txid, update_last_seen);
755-
}
756-
}
757-
758-
changeset.anchors = update.anchors.difference(&self.anchors).cloned().collect();
759-
760-
changeset
761-
}
762738
}
763739

764740
impl<A: Anchor> TxGraph<A> {

crates/chain/tests/test_tx_graph.rs

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -301,59 +301,28 @@ fn insert_tx_can_retrieve_full_tx_from_graph() {
301301
#[test]
302302
fn insert_tx_displaces_txouts() {
303303
let mut tx_graph = TxGraph::<()>::default();
304+
304305
let tx = Transaction {
305306
version: transaction::Version::ONE,
306307
lock_time: absolute::LockTime::ZERO,
307308
input: vec![],
308309
output: vec![TxOut {
309310
value: Amount::from_sat(42_000),
310-
script_pubkey: ScriptBuf::new(),
311+
script_pubkey: ScriptBuf::default(),
311312
}],
312313
};
314+
let txid = tx.compute_txid();
315+
let outpoint = OutPoint::new(txid, 0);
316+
let txout = tx.output.first().unwrap();
313317

314-
let changeset = tx_graph.insert_txout(
315-
OutPoint {
316-
txid: tx.compute_txid(),
317-
vout: 0,
318-
},
319-
TxOut {
320-
value: Amount::from_sat(1_337_000),
321-
script_pubkey: ScriptBuf::default(),
322-
},
323-
);
324-
318+
let changeset = tx_graph.insert_txout(outpoint, txout.clone());
325319
assert!(!changeset.is_empty());
326320

327-
let _ = tx_graph.insert_txout(
328-
OutPoint {
329-
txid: tx.compute_txid(),
330-
vout: 0,
331-
},
332-
TxOut {
333-
value: Amount::from_sat(1_000_000_000),
334-
script_pubkey: ScriptBuf::new(),
335-
},
336-
);
337-
338-
let _changeset = tx_graph.insert_tx(tx.clone());
339-
340-
assert_eq!(
341-
tx_graph
342-
.get_txout(OutPoint {
343-
txid: tx.compute_txid(),
344-
vout: 0
345-
})
346-
.unwrap()
347-
.value,
348-
Amount::from_sat(42_000)
349-
);
350-
assert_eq!(
351-
tx_graph.get_txout(OutPoint {
352-
txid: tx.compute_txid(),
353-
vout: 1
354-
}),
355-
None
356-
);
321+
let changeset = tx_graph.insert_tx(tx.clone());
322+
assert_eq!(changeset.txs.len(), 1);
323+
assert!(changeset.txouts.is_empty());
324+
assert!(tx_graph.get_tx(txid).is_some());
325+
assert_eq!(tx_graph.get_txout(outpoint), Some(txout));
357326
}
358327

359328
#[test]
@@ -385,7 +354,7 @@ fn insert_txout_does_not_displace_tx() {
385354
let _ = tx_graph.insert_txout(
386355
OutPoint {
387356
txid: tx.compute_txid(),
388-
vout: 0,
357+
vout: 1,
389358
},
390359
TxOut {
391360
value: Amount::from_sat(1_000_000_000),

0 commit comments

Comments
 (0)