1
1
//! Unbroadcasted transaction queue.
2
2
3
+ use core:: convert:: Infallible ;
4
+
3
5
use alloc:: sync:: Arc ;
4
6
5
7
use alloc:: vec:: Vec ;
6
8
use bitcoin:: OutPoint ;
7
9
use bitcoin:: Transaction ;
8
10
use chain:: tx_graph;
11
+ use chain:: tx_graph:: TxNode ;
9
12
use chain:: Anchor ;
13
+ use chain:: BlockId ;
10
14
use chain:: CanonicalIter ;
11
15
use chain:: CanonicalReason ;
12
16
use chain:: ChainOracle ;
13
17
use chain:: ChainPosition ;
18
+ use chain:: ObservedIn ;
14
19
use chain:: TxGraph ;
15
20
16
21
use crate :: collections:: BTreeMap ;
@@ -21,10 +26,11 @@ use crate::collections::VecDeque;
21
26
use bdk_chain:: bdk_core:: Merge ;
22
27
use bitcoin:: Txid ;
23
28
29
+ /// A consistent view of transactions.
24
30
#[ derive( Debug ) ]
25
31
pub struct CanonicalView < A > {
26
- pub txs : HashMap < Txid , ( Arc < Transaction > , CanonicalReason < A > ) > ,
27
- pub spends : HashMap < OutPoint , Txid > ,
32
+ pub ( crate ) txs : HashMap < Txid , ( Arc < Transaction > , CanonicalReason < A > ) > ,
33
+ pub ( crate ) spends : HashMap < OutPoint , Txid > ,
28
34
}
29
35
30
36
impl < A > Default for CanonicalView < A > {
@@ -37,7 +43,7 @@ impl<A> Default for CanonicalView<A> {
37
43
}
38
44
39
45
impl < A > CanonicalView < A > {
40
- pub fn from_iter < C > ( iter : CanonicalIter < ' _ , A , C > ) -> Result < Self , C :: Error >
46
+ pub ( crate ) fn from_iter < C > ( iter : CanonicalIter < ' _ , A , C > ) -> Result < Self , C :: Error >
41
47
where
42
48
A : Anchor ,
43
49
C : ChainOracle ,
@@ -53,11 +59,49 @@ impl<A> CanonicalView<A> {
53
59
Ok ( view)
54
60
}
55
61
62
+ /// Return the transaction that spends the given `op`.
56
63
pub fn spend ( & self , op : OutPoint ) -> Option < ( Txid , Arc < Transaction > , & CanonicalReason < A > ) > {
57
64
let txid = self . spends . get ( & op) ?;
58
65
let ( tx, reason) = self . txs . get ( txid) ?;
59
66
Some ( ( * txid, tx. clone ( ) , reason) )
60
67
}
68
+
69
+ /// Iterate all descendants of the given transaction in the [`CanonicalView`], avoiding
70
+ /// duplicates.
71
+ fn descendants (
72
+ & self ,
73
+ tx : impl AsRef < Transaction > ,
74
+ ) -> impl Iterator < Item = ( Txid , Arc < Transaction > , & CanonicalReason < A > ) > {
75
+ let tx: & Transaction = tx. as_ref ( ) ;
76
+ let txid = tx. compute_txid ( ) ;
77
+
78
+ let mut visited = HashSet :: < Txid > :: new ( ) ;
79
+ visited. insert ( txid) ;
80
+
81
+ let mut outpoints = core:: iter:: repeat_n ( txid, tx. output . len ( ) )
82
+ . zip ( 0_u32 ..)
83
+ . map ( |( txid, vout) | OutPoint :: new ( txid, vout) )
84
+ . collect :: < Vec < _ > > ( ) ;
85
+
86
+ core:: iter:: from_fn ( move || {
87
+ while let Some ( op) = outpoints. pop ( ) {
88
+ let ( txid, tx, reason) = match self . spend ( op) {
89
+ Some ( spent_by) => spent_by,
90
+ None => continue ,
91
+ } ;
92
+ if !visited. insert ( txid) {
93
+ continue ;
94
+ }
95
+ outpoints. extend (
96
+ core:: iter:: repeat_n ( txid, tx. output . len ( ) )
97
+ . zip ( 0_u32 ..)
98
+ . map ( |( txid, vout) | OutPoint :: new ( txid, vout) ) ,
99
+ ) ;
100
+ return Some ( ( txid, tx, reason) ) ;
101
+ }
102
+ None
103
+ } )
104
+ }
61
105
}
62
106
63
107
/// Indicates whether a transaction was observed in the network.
@@ -83,8 +127,8 @@ impl NetworkSeen {
83
127
///
84
128
/// This struct models an input that attempts to spend an output via a transaction path
85
129
/// that is not part of the canonical network view (e.g., evicted, conflicted, or unknown).
86
- #[ derive( Debug , Clone , Default ) ]
87
- pub struct UncanonicalSpendInfo < A > {
130
+ #[ derive( Debug , Clone ) ]
131
+ pub struct SpendInfo < A > {
88
132
/// Non-canonical ancestor transactions reachable from this input.
89
133
///
90
134
/// Each entry maps an ancestor `Txid` to its observed status in the network.
@@ -95,12 +139,177 @@ pub struct UncanonicalSpendInfo<A> {
95
139
96
140
/// Canonical transactions that conflict with this spend.
97
141
///
98
- /// This may be a direct conflict or a conflict with one of the `uncanonical_ancestors`.
99
- /// The value is a tuple of (conflict distance, chain position).
142
+ /// This may be a direct conflict, a conflict with one of the [`uncanonical_ancestors`], or a
143
+ /// canonical descendant of a conflict (which are also conflicts). The value is the chain
144
+ /// position of the conflict.
100
145
///
101
- /// Descendants of conflicts are also conflicts. These transactions will have the same distance
102
- /// value as their conflicting parent.
103
- pub conflicting_txs : BTreeMap < Txid , ( u32 , ChainPosition < A > ) > ,
146
+ /// [`uncanonical_ancestors`]: Self::uncanonical_ancestors
147
+ pub conflicting_txs : BTreeMap < Txid , ChainPosition < A > > ,
148
+ }
149
+
150
+ impl < A > Default for SpendInfo < A > {
151
+ fn default ( ) -> Self {
152
+ Self {
153
+ uncanonical_ancestors : BTreeMap :: new ( ) ,
154
+ conflicting_txs : BTreeMap :: new ( ) ,
155
+ }
156
+ }
157
+ }
158
+
159
+ impl < A : Anchor > SpendInfo < A > {
160
+ pub ( crate ) fn new < C > (
161
+ chain : & C ,
162
+ chain_tip : BlockId ,
163
+ tx_graph : & TxGraph < A > ,
164
+ network_view : & CanonicalView < A > ,
165
+ op : OutPoint ,
166
+ ) -> Self
167
+ where
168
+ C : ChainOracle < Error = Infallible > ,
169
+ {
170
+ use crate :: collections:: btree_map:: Entry ;
171
+
172
+ let mut spend_info = Self :: default ( ) ;
173
+
174
+ let mut visited = HashSet :: < OutPoint > :: new ( ) ;
175
+ let mut stack = Vec :: < OutPoint > :: new ( ) ;
176
+ stack. push ( op) ;
177
+
178
+ while let Some ( prev_op) = stack. pop ( ) {
179
+ if !visited. insert ( prev_op) {
180
+ // Outpoint already visited.
181
+ continue ;
182
+ }
183
+ if network_view. txs . contains_key ( & prev_op. txid ) {
184
+ // Tx is already canonical.
185
+ continue ;
186
+ }
187
+
188
+ let prev_tx_node = match tx_graph. get_tx_node ( prev_op. txid ) {
189
+ Some ( prev_tx) => prev_tx,
190
+ // Tx not known by tx-graph.
191
+ None => continue ,
192
+ } ;
193
+
194
+ match spend_info. uncanonical_ancestors . entry ( prev_op. txid ) {
195
+ Entry :: Vacant ( entry) => entry. insert (
196
+ if !prev_tx_node. anchors . is_empty ( ) || prev_tx_node. last_seen . is_some ( ) {
197
+ NetworkSeen :: Seen
198
+ } else {
199
+ NetworkSeen :: NeverSeen
200
+ } ,
201
+ ) ,
202
+ // Tx already visited.
203
+ Entry :: Occupied ( _) => continue ,
204
+ } ;
205
+
206
+ // Find conflicts to populate `conflicting_txs`.
207
+ if let Some ( ( conflict_txid, conflict_tx, reason) ) = network_view. spend ( prev_op) {
208
+ let conflict_tx_entry = match spend_info. conflicting_txs . entry ( conflict_txid) {
209
+ Entry :: Vacant ( vacant_entry) => vacant_entry,
210
+ // Skip if conflicting tx already visited.
211
+ Entry :: Occupied ( _) => continue ,
212
+ } ;
213
+ let conflict_tx_node = match tx_graph. get_tx_node ( conflict_txid) {
214
+ Some ( tx_node) => tx_node,
215
+ // Skip if conflict tx does not exist in our graph.
216
+ None => continue ,
217
+ } ;
218
+ conflict_tx_entry. insert ( Self :: get_pos (
219
+ chain,
220
+ chain_tip,
221
+ & conflict_tx_node,
222
+ reason,
223
+ ) ) ;
224
+
225
+ // Find descendants of `conflict_tx` too.
226
+ for ( conflict_txid, _, reason) in network_view. descendants ( conflict_tx) {
227
+ let conflict_tx_entry = match spend_info. conflicting_txs . entry ( conflict_txid) {
228
+ Entry :: Vacant ( vacant_entry) => vacant_entry,
229
+ // Skip if conflicting tx already visited.
230
+ Entry :: Occupied ( _) => continue ,
231
+ } ;
232
+ let conflict_tx_node = match tx_graph. get_tx_node ( conflict_txid) {
233
+ Some ( tx_node) => tx_node,
234
+ // Skip if conflict tx does not exist in our graph.
235
+ None => continue ,
236
+ } ;
237
+ conflict_tx_entry. insert ( Self :: get_pos (
238
+ chain,
239
+ chain_tip,
240
+ & conflict_tx_node,
241
+ reason,
242
+ ) ) ;
243
+ }
244
+ }
245
+
246
+ stack. extend (
247
+ prev_tx_node
248
+ . tx
249
+ . input
250
+ . iter ( )
251
+ . map ( |txin| txin. previous_output ) ,
252
+ ) ;
253
+ }
254
+
255
+ spend_info
256
+ }
257
+
258
+ fn get_pos < C > (
259
+ chain : & C ,
260
+ chain_tip : BlockId ,
261
+ tx_node : & TxNode < ' _ , Arc < Transaction > , A > ,
262
+ canonical_reason : & CanonicalReason < A > ,
263
+ ) -> ChainPosition < A >
264
+ where
265
+ C : ChainOracle < Error = Infallible > ,
266
+ {
267
+ let maybe_direct_anchor = tx_node
268
+ . anchors
269
+ . iter ( )
270
+ . find ( |a| {
271
+ chain
272
+ . is_block_in_chain ( a. anchor_block ( ) , chain_tip)
273
+ . expect ( "infallible" )
274
+ . unwrap_or ( false )
275
+ } )
276
+ . cloned ( ) ;
277
+ match maybe_direct_anchor {
278
+ Some ( anchor) => ChainPosition :: Confirmed {
279
+ anchor,
280
+ transitively : None ,
281
+ } ,
282
+ None => match canonical_reason. clone ( ) {
283
+ CanonicalReason :: Assumed { .. } => {
284
+ debug_assert ! (
285
+ false ,
286
+ "network view must not have any assumed-canonical txs"
287
+ ) ;
288
+ ChainPosition :: Unconfirmed {
289
+ first_seen : None ,
290
+ last_seen : None ,
291
+ }
292
+ }
293
+ CanonicalReason :: Anchor { anchor, descendant } => ChainPosition :: Confirmed {
294
+ anchor,
295
+ transitively : descendant,
296
+ } ,
297
+ CanonicalReason :: ObservedIn { observed_in, .. } => ChainPosition :: Unconfirmed {
298
+ first_seen : tx_node. first_seen ,
299
+ last_seen : match observed_in {
300
+ ObservedIn :: Block ( _) => None ,
301
+ ObservedIn :: Mempool ( last_seen) => Some ( last_seen) ,
302
+ } ,
303
+ } ,
304
+ } ,
305
+ }
306
+ }
307
+
308
+ /// If the spend info is empty, then it can belong in the canonical history without displacing
309
+ /// existing transactions or need to add additional transactions other than itself.
310
+ pub fn is_empty ( & self ) -> bool {
311
+ self . uncanonical_ancestors . is_empty ( ) && self . conflicting_txs . is_empty ( )
312
+ }
104
313
}
105
314
106
315
/// Tracked and uncanonical transaction.
@@ -113,7 +322,7 @@ pub struct UncanonicalTx<A> {
113
322
/// Whether the transaction was one seen by the network.
114
323
pub network_seen : NetworkSeen ,
115
324
/// Spends, identified by prevout, which are uncanonical.
116
- pub uncanonical_spends : BTreeMap < OutPoint , UncanonicalSpendInfo < A > > ,
325
+ pub uncanonical_spends : BTreeMap < OutPoint , SpendInfo < A > > ,
117
326
}
118
327
119
328
impl < A : Anchor > UncanonicalTx < A > {
@@ -154,20 +363,22 @@ impl<A: Anchor> UncanonicalTx<A> {
154
363
self . uncanonical_spends
155
364
. values ( )
156
365
. flat_map ( |spend| & spend. conflicting_txs )
157
- . map ( |( & txid, ( _ , pos) ) | ( txid, pos) )
366
+ . map ( |( & txid, pos) | ( txid, pos) )
158
367
. filter ( {
159
368
let mut dedup = HashSet :: < Txid > :: new ( ) ;
160
369
move |( txid, _) | dedup. insert ( * txid)
161
370
} )
162
371
}
163
372
373
+ /// Iterate over confirmed, network-canonical txids which conflict with this transaction.
164
374
pub fn confirmed_conflicts ( & self ) -> impl Iterator < Item = ( Txid , & A ) > {
165
375
self . conflicts ( ) . filter_map ( |( txid, pos) | match pos {
166
376
ChainPosition :: Confirmed { anchor, .. } => Some ( ( txid, anchor) ) ,
167
377
ChainPosition :: Unconfirmed { .. } => None ,
168
378
} )
169
379
}
170
380
381
+ /// Iterate over unconfirmed, network-canonical txids which conflict with this transaction.
171
382
pub fn unconfirmed_conflicts ( & self ) -> impl Iterator < Item = Txid > + ' _ {
172
383
self . conflicts ( ) . filter_map ( |( txid, pos) | match pos {
173
384
ChainPosition :: Confirmed { .. } => None ,
@@ -185,20 +396,20 @@ impl<A: Anchor> UncanonicalTx<A> {
185
396
. map ( |( & txid, & network_seen) | ( txid, network_seen) )
186
397
}
187
398
399
+ /// Whether this transaction conflicts with network-canonical transactions.
188
400
pub fn contains_conflicts ( & self ) -> bool {
189
401
self . conflicts ( ) . next ( ) . is_some ( )
190
402
}
191
403
404
+ /// Whether this transaction conflicts with confirmed, network-canonical transactions.
192
405
pub fn contains_confirmed_conflicts ( & self ) -> bool {
193
406
self . confirmed_conflicts ( ) . next ( ) . is_some ( )
194
407
}
195
408
}
196
409
197
- /// An ordered unbroadcasted staging area.
198
- ///
199
- /// It is ordered in case of RBF txs.
410
+ /// An ordered tracking area for uncanonical transactions.
200
411
#[ derive( Debug , Clone , Default ) ]
201
- pub struct CanonicalizationTracker {
412
+ pub struct IntentTracker {
202
413
/// Tracks the order that transactions are added.
203
414
order : VecDeque < Txid > ,
204
415
@@ -233,10 +444,10 @@ impl Merge for ChangeSet {
233
444
}
234
445
}
235
446
236
- impl CanonicalizationTracker {
447
+ impl IntentTracker {
237
448
/// Construct [`Unbroadcasted`] from the given `changeset`.
238
449
pub fn from_changeset ( changeset : ChangeSet ) -> Self {
239
- let mut out = CanonicalizationTracker :: default ( ) ;
450
+ let mut out = IntentTracker :: default ( ) ;
240
451
out. apply_changeset ( changeset) ;
241
452
out
242
453
}
0 commit comments