1- use crate :: collections:: { hash_map , HashMap , HashSet , VecDeque } ;
1+ use crate :: collections:: { HashMap , HashSet , VecDeque } ;
22use crate :: tx_graph:: { TxAncestors , TxDescendants } ;
33use crate :: { Anchor , ChainOracle , TxGraph } ;
44use alloc:: boxed:: Box ;
55use alloc:: collections:: BTreeSet ;
66use alloc:: sync:: Arc ;
7+ use alloc:: vec:: Vec ;
78use bdk_core:: BlockId ;
89use bitcoin:: { Transaction , Txid } ;
910
11+ type CanonicalMap < A > = HashMap < Txid , ( Arc < Transaction > , CanonicalReason < A > ) > ;
12+ type NotCanonicalSet = HashSet < Txid > ;
13+
1014/// Iterates over canonical txs.
1115pub struct CanonicalIter < ' g , A , C > {
1216 tx_graph : & ' g TxGraph < A > ,
@@ -18,8 +22,8 @@ pub struct CanonicalIter<'g, A, C> {
1822 unprocessed_txs_with_last_seens : Box < dyn Iterator < Item = ( Txid , Arc < Transaction > , u64 ) > + ' g > ,
1923 unprocessed_txs_left_over : VecDeque < ( Txid , Arc < Transaction > , u32 ) > ,
2024
21- canonical : HashMap < Txid , ( Arc < Transaction > , CanonicalReason < A > ) > ,
22- not_canonical : HashSet < Txid > ,
25+ canonical : CanonicalMap < A > ,
26+ not_canonical : NotCanonicalSet ,
2327
2428 queue : VecDeque < Txid > ,
2529}
@@ -87,27 +91,49 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
8791 Ok ( ( ) )
8892 }
8993
90- /// Marks a transaction and it's ancestors as canonical. Mark all conflicts of these as
94+ /// Marks `tx` and it's ancestors as canonical and mark all conflicts of these as
9195 /// `not_canonical`.
96+ ///
97+ /// The exception is when it is discovered that `tx` double spends itself (i.e. two of it's
98+ /// inputs conflict with each other), then no changes will be made.
99+ ///
100+ /// The logic works by having two loops where one is nested in another.
101+ /// * The outer loop iterates through ancestors of `tx` (including `tx`). We can transitively
102+ /// assume that all ancestors of `tx` are also canonical.
103+ /// * The inner loop loops through conflicts of ancestors of `tx`. Any descendants of conflicts
104+ /// are also conflicts and are transitively considered non-canonical.
105+ ///
106+ /// If the inner loop ends up marking `tx` as non-canonical, then we know that it double spends
107+ /// itself.
92108 fn mark_canonical ( & mut self , txid : Txid , tx : Arc < Transaction > , reason : CanonicalReason < A > ) {
93109 let starting_txid = txid;
94- let mut is_root = true ;
95- TxAncestors :: new_include_root (
110+ let mut is_starting_tx = true ;
111+
112+ // We keep track of changes made so far so that we can undo it later in case we detect that
113+ // `tx` double spends itself.
114+ let mut detected_self_double_spend = false ;
115+ let mut undo_not_canonical = Vec :: < Txid > :: new ( ) ;
116+
117+ // `staged_queue` doubles as the `undo_canonical` data.
118+ let staged_queue = TxAncestors :: new_include_root (
96119 self . tx_graph ,
97120 tx,
98- |_: usize , tx : Arc < Transaction > | -> Option < ( ) > {
121+ |_: usize , tx : Arc < Transaction > | -> Option < Txid > {
99122 let this_txid = tx. compute_txid ( ) ;
100- let this_reason = if is_root {
101- is_root = false ;
123+ let this_reason = if is_starting_tx {
124+ is_starting_tx = false ;
102125 reason. clone ( )
103126 } else {
104127 reason. to_transitive ( starting_txid)
105128 } ;
129+
130+ use crate :: collections:: hash_map:: Entry ;
106131 let canonical_entry = match self . canonical . entry ( this_txid) {
107132 // Already visited tx before, exit early.
108- hash_map :: Entry :: Occupied ( _) => return None ,
109- hash_map :: Entry :: Vacant ( entry) => entry,
133+ Entry :: Occupied ( _) => return None ,
134+ Entry :: Vacant ( entry) => entry,
110135 } ;
136+
111137 // Any conflicts with a canonical tx can be added to `not_canonical`. Descendants
112138 // of `not_canonical` txs can also be added to `not_canonical`.
113139 for ( _, conflict_txid) in self . tx_graph . direct_conflicts ( & tx) {
@@ -116,6 +142,7 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
116142 conflict_txid,
117143 |_: usize , txid : Txid | -> Option < ( ) > {
118144 if self . not_canonical . insert ( txid) {
145+ undo_not_canonical. push ( txid) ;
119146 Some ( ( ) )
120147 } else {
121148 None
@@ -124,12 +151,28 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
124151 )
125152 . run_until_finished ( )
126153 }
154+
155+ if self . not_canonical . contains ( & this_txid) {
156+ // Early exit if self-double-spend is detected.
157+ detected_self_double_spend = true ;
158+ return None ;
159+ }
127160 canonical_entry. insert ( ( tx, this_reason) ) ;
128- self . queue . push_back ( this_txid) ;
129- Some ( ( ) )
161+ Some ( this_txid)
130162 } ,
131163 )
132- . run_until_finished ( )
164+ . collect :: < Vec < Txid > > ( ) ;
165+
166+ if detected_self_double_spend {
167+ for txid in staged_queue {
168+ self . canonical . remove ( & txid) ;
169+ }
170+ for txid in undo_not_canonical {
171+ self . not_canonical . remove ( & txid) ;
172+ }
173+ } else {
174+ self . queue . extend ( staged_queue) ;
175+ }
133176 }
134177}
135178
0 commit comments