1
- use crate :: collections:: { hash_map , HashMap , HashSet , VecDeque } ;
1
+ use crate :: collections:: { HashMap , HashSet , VecDeque } ;
2
2
use crate :: tx_graph:: { TxAncestors , TxDescendants } ;
3
3
use crate :: { Anchor , ChainOracle , TxGraph } ;
4
4
use alloc:: boxed:: Box ;
5
5
use alloc:: collections:: BTreeSet ;
6
6
use alloc:: sync:: Arc ;
7
+ use alloc:: vec:: Vec ;
7
8
use bdk_core:: BlockId ;
8
9
use bitcoin:: { Transaction , Txid } ;
9
10
11
+ type CanonicalMap < A > = HashMap < Txid , ( Arc < Transaction > , CanonicalReason < A > ) > ;
12
+ type NotCanonicalSet = HashSet < Txid > ;
13
+
10
14
/// Iterates over canonical txs.
11
15
pub struct CanonicalIter < ' g , A , C > {
12
16
tx_graph : & ' g TxGraph < A > ,
@@ -18,8 +22,8 @@ pub struct CanonicalIter<'g, A, C> {
18
22
unprocessed_txs_with_last_seens : Box < dyn Iterator < Item = ( Txid , Arc < Transaction > , u64 ) > + ' g > ,
19
23
unprocessed_txs_left_over : VecDeque < ( Txid , Arc < Transaction > , u32 ) > ,
20
24
21
- canonical : HashMap < Txid , ( Arc < Transaction > , CanonicalReason < A > ) > ,
22
- not_canonical : HashSet < Txid > ,
25
+ canonical : CanonicalMap < A > ,
26
+ not_canonical : NotCanonicalSet ,
23
27
24
28
queue : VecDeque < Txid > ,
25
29
}
@@ -87,27 +91,49 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
87
91
Ok ( ( ) )
88
92
}
89
93
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
91
95
/// `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.
92
108
fn mark_canonical ( & mut self , txid : Txid , tx : Arc < Transaction > , reason : CanonicalReason < A > ) {
93
109
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 (
96
119
self . tx_graph ,
97
120
tx,
98
- |_: usize , tx : Arc < Transaction > | -> Option < ( ) > {
121
+ |_: usize , tx : Arc < Transaction > | -> Option < Txid > {
99
122
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 ;
102
125
reason. clone ( )
103
126
} else {
104
127
reason. to_transitive ( starting_txid)
105
128
} ;
129
+
130
+ use crate :: collections:: hash_map:: Entry ;
106
131
let canonical_entry = match self . canonical . entry ( this_txid) {
107
132
// 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,
110
135
} ;
136
+
111
137
// Any conflicts with a canonical tx can be added to `not_canonical`. Descendants
112
138
// of `not_canonical` txs can also be added to `not_canonical`.
113
139
for ( _, conflict_txid) in self . tx_graph . direct_conflicts ( & tx) {
@@ -116,6 +142,7 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
116
142
conflict_txid,
117
143
|_: usize , txid : Txid | -> Option < ( ) > {
118
144
if self . not_canonical . insert ( txid) {
145
+ undo_not_canonical. push ( txid) ;
119
146
Some ( ( ) )
120
147
} else {
121
148
None
@@ -124,12 +151,28 @@ impl<'g, A: Anchor, C: ChainOracle> CanonicalIter<'g, A, C> {
124
151
)
125
152
. run_until_finished ( )
126
153
}
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
+ }
127
160
canonical_entry. insert ( ( tx, this_reason) ) ;
128
- self . queue . push_back ( this_txid) ;
129
- Some ( ( ) )
161
+ Some ( this_txid)
130
162
} ,
131
163
)
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
+ }
133
176
}
134
177
}
135
178
0 commit comments