11//! Unbroadcasted transaction queue.
22
3+ use core:: convert:: Infallible ;
4+
35use alloc:: sync:: Arc ;
46
57use alloc:: vec:: Vec ;
68use bitcoin:: OutPoint ;
79use bitcoin:: Transaction ;
810use chain:: tx_graph;
11+ use chain:: tx_graph:: TxNode ;
912use chain:: Anchor ;
13+ use chain:: BlockId ;
1014use chain:: CanonicalIter ;
1115use chain:: CanonicalReason ;
1216use chain:: ChainOracle ;
1317use chain:: ChainPosition ;
18+ use chain:: ObservedIn ;
1419use chain:: TxGraph ;
1520
1621use crate :: collections:: BTreeMap ;
@@ -21,10 +26,11 @@ use crate::collections::VecDeque;
2126use bdk_chain:: bdk_core:: Merge ;
2227use bitcoin:: Txid ;
2328
29+ /// A consistent view of transactions.
2430#[ derive( Debug ) ]
2531pub 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 > ,
2834}
2935
3036impl < A > Default for CanonicalView < A > {
@@ -37,7 +43,7 @@ impl<A> Default for CanonicalView<A> {
3743}
3844
3945impl < 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 >
4147 where
4248 A : Anchor ,
4349 C : ChainOracle ,
@@ -53,11 +59,49 @@ impl<A> CanonicalView<A> {
5359 Ok ( view)
5460 }
5561
62+ /// Return the transaction that spends the given `op`.
5663 pub fn spend ( & self , op : OutPoint ) -> Option < ( Txid , Arc < Transaction > , & CanonicalReason < A > ) > {
5764 let txid = self . spends . get ( & op) ?;
5865 let ( tx, reason) = self . txs . get ( txid) ?;
5966 Some ( ( * txid, tx. clone ( ) , reason) )
6067 }
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+ }
61105}
62106
63107/// Indicates whether a transaction was observed in the network.
@@ -83,8 +127,8 @@ impl NetworkSeen {
83127///
84128/// This struct models an input that attempts to spend an output via a transaction path
85129/// 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 > {
88132 /// Non-canonical ancestor transactions reachable from this input.
89133 ///
90134 /// Each entry maps an ancestor `Txid` to its observed status in the network.
@@ -95,12 +139,177 @@ pub struct UncanonicalSpendInfo<A> {
95139
96140 /// Canonical transactions that conflict with this spend.
97141 ///
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.
100145 ///
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+ }
104313}
105314
106315/// Tracked and uncanonical transaction.
@@ -113,7 +322,7 @@ pub struct UncanonicalTx<A> {
113322 /// Whether the transaction was one seen by the network.
114323 pub network_seen : NetworkSeen ,
115324 /// Spends, identified by prevout, which are uncanonical.
116- pub uncanonical_spends : BTreeMap < OutPoint , UncanonicalSpendInfo < A > > ,
325+ pub uncanonical_spends : BTreeMap < OutPoint , SpendInfo < A > > ,
117326}
118327
119328impl < A : Anchor > UncanonicalTx < A > {
@@ -154,20 +363,22 @@ impl<A: Anchor> UncanonicalTx<A> {
154363 self . uncanonical_spends
155364 . values ( )
156365 . flat_map ( |spend| & spend. conflicting_txs )
157- . map ( |( & txid, ( _ , pos) ) | ( txid, pos) )
366+ . map ( |( & txid, pos) | ( txid, pos) )
158367 . filter ( {
159368 let mut dedup = HashSet :: < Txid > :: new ( ) ;
160369 move |( txid, _) | dedup. insert ( * txid)
161370 } )
162371 }
163372
373+ /// Iterate over confirmed, network-canonical txids which conflict with this transaction.
164374 pub fn confirmed_conflicts ( & self ) -> impl Iterator < Item = ( Txid , & A ) > {
165375 self . conflicts ( ) . filter_map ( |( txid, pos) | match pos {
166376 ChainPosition :: Confirmed { anchor, .. } => Some ( ( txid, anchor) ) ,
167377 ChainPosition :: Unconfirmed { .. } => None ,
168378 } )
169379 }
170380
381+ /// Iterate over unconfirmed, network-canonical txids which conflict with this transaction.
171382 pub fn unconfirmed_conflicts ( & self ) -> impl Iterator < Item = Txid > + ' _ {
172383 self . conflicts ( ) . filter_map ( |( txid, pos) | match pos {
173384 ChainPosition :: Confirmed { .. } => None ,
@@ -185,20 +396,20 @@ impl<A: Anchor> UncanonicalTx<A> {
185396 . map ( |( & txid, & network_seen) | ( txid, network_seen) )
186397 }
187398
399+ /// Whether this transaction conflicts with network-canonical transactions.
188400 pub fn contains_conflicts ( & self ) -> bool {
189401 self . conflicts ( ) . next ( ) . is_some ( )
190402 }
191403
404+ /// Whether this transaction conflicts with confirmed, network-canonical transactions.
192405 pub fn contains_confirmed_conflicts ( & self ) -> bool {
193406 self . confirmed_conflicts ( ) . next ( ) . is_some ( )
194407 }
195408}
196409
197- /// An ordered unbroadcasted staging area.
198- ///
199- /// It is ordered in case of RBF txs.
410+ /// An ordered tracking area for uncanonical transactions.
200411#[ derive( Debug , Clone , Default ) ]
201- pub struct CanonicalizationTracker {
412+ pub struct IntentTracker {
202413 /// Tracks the order that transactions are added.
203414 order : VecDeque < Txid > ,
204415
@@ -233,10 +444,10 @@ impl Merge for ChangeSet {
233444 }
234445}
235446
236- impl CanonicalizationTracker {
447+ impl IntentTracker {
237448 /// Construct [`Unbroadcasted`] from the given `changeset`.
238449 pub fn from_changeset ( changeset : ChangeSet ) -> Self {
239- let mut out = CanonicalizationTracker :: default ( ) ;
450+ let mut out = IntentTracker :: default ( ) ;
240451 out. apply_changeset ( changeset) ;
241452 out
242453 }
0 commit comments