2424
2525use crate :: collections:: HashMap ;
2626use alloc:: sync:: Arc ;
27- use core:: { fmt, ops:: RangeBounds } ;
28-
2927use alloc:: vec:: Vec ;
28+ use core:: { fmt, ops:: RangeBounds } ;
3029
3130use bdk_core:: BlockId ;
32- use bitcoin:: { Amount , OutPoint , ScriptBuf , Transaction , Txid } ;
31+ use bitcoin:: {
32+ constants:: COINBASE_MATURITY , Amount , OutPoint , ScriptBuf , Transaction , TxOut , Txid ,
33+ } ;
3334
34- use crate :: { spk_txout:: SpkTxOutIndex , Anchor , Balance , ChainPosition , FullTxOut } ;
35+ use crate :: { spk_txout:: SpkTxOutIndex , Anchor , Balance , CanonicalViewTask , ChainPosition , TxGraph } ;
3536
3637/// A single canonical transaction with its position.
3738///
@@ -68,6 +69,104 @@ impl<P: Ord> PartialOrd for CanonicalTx<P> {
6869 }
6970}
7071
72+ /// A canonical transaction output with position and spend information.
73+ ///
74+ /// The position type `P` is generic — it can be [`ChainPosition`] for resolved views,
75+ /// or [`CanonicalReason`](crate::canonical_task::CanonicalReason) for unresolved canonicalization
76+ /// results.
77+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
78+ pub struct CanonicalTxOut < P > {
79+ /// The position of the transaction in `outpoint` in the overall chain.
80+ pub pos : P ,
81+ /// The location of the `TxOut`.
82+ pub outpoint : OutPoint ,
83+ /// The `TxOut`.
84+ pub txout : TxOut ,
85+ /// The txid and position of the transaction (if any) that has spent this output.
86+ pub spent_by : Option < ( P , Txid ) > ,
87+ /// Whether this output is on a coinbase transaction.
88+ pub is_on_coinbase : bool ,
89+ }
90+
91+ impl < P : Ord > Ord for CanonicalTxOut < P > {
92+ fn cmp ( & self , other : & Self ) -> core:: cmp:: Ordering {
93+ self . pos
94+ . cmp ( & other. pos )
95+ // Tie-break with `outpoint` and `spent_by`.
96+ . then_with ( || self . outpoint . cmp ( & other. outpoint ) )
97+ . then_with ( || self . spent_by . cmp ( & other. spent_by ) )
98+ }
99+ }
100+
101+ impl < P : Ord > PartialOrd for CanonicalTxOut < P > {
102+ fn partial_cmp ( & self , other : & Self ) -> Option < core:: cmp:: Ordering > {
103+ Some ( self . cmp ( other) )
104+ }
105+ }
106+
107+ impl < A : Anchor > CanonicalTxOut < ChainPosition < A > > {
108+ /// Whether the `txout` is considered mature.
109+ ///
110+ /// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
111+ /// method may return false-negatives. In other words, interpreted confirmation count may be
112+ /// less than the actual value.
113+ ///
114+ /// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
115+ pub fn is_mature ( & self , tip : u32 ) -> bool {
116+ if self . is_on_coinbase {
117+ let conf_height = match self . pos . confirmation_height_upper_bound ( ) {
118+ Some ( height) => height,
119+ None => {
120+ debug_assert ! ( false , "coinbase tx can never be unconfirmed" ) ;
121+ return false ;
122+ }
123+ } ;
124+ let age = tip. saturating_sub ( conf_height) ;
125+ if age + 1 < COINBASE_MATURITY {
126+ return false ;
127+ }
128+ }
129+
130+ true
131+ }
132+
133+ /// Whether the utxo is/was/will be spendable with chain `tip`.
134+ ///
135+ /// This method does not take into account the lock time.
136+ ///
137+ /// Depending on the implementation of [`confirmation_height_upper_bound`] in [`Anchor`], this
138+ /// method may return false-negatives. In other words, interpreted confirmation count may be
139+ /// less than the actual value.
140+ ///
141+ /// [`confirmation_height_upper_bound`]: Anchor::confirmation_height_upper_bound
142+ pub fn is_confirmed_and_spendable ( & self , tip : u32 ) -> bool {
143+ if !self . is_mature ( tip) {
144+ return false ;
145+ }
146+
147+ let conf_height = match self . pos . confirmation_height_upper_bound ( ) {
148+ Some ( height) => height,
149+ None => return false ,
150+ } ;
151+ if conf_height > tip {
152+ return false ;
153+ }
154+
155+ // if the spending tx is confirmed within tip height, the txout is no longer spendable
156+ if let Some ( spend_height) = self
157+ . spent_by
158+ . as_ref ( )
159+ . and_then ( |( pos, _) | pos. confirmation_height_upper_bound ( ) )
160+ {
161+ if spend_height <= tip {
162+ return false ;
163+ }
164+ }
165+
166+ true
167+ }
168+ }
169+
71170/// Canonical set of transactions from a [`TxGraph`].
72171///
73172/// `Canonical` provides an ordered, conflict-resolved set of transactions. It determines
@@ -151,15 +250,15 @@ impl<A, P: Clone> Canonical<A, P> {
151250 /// - The transaction doesn't exist in the canonical set
152251 /// - The output index is out of bounds
153252 /// - The transaction was excluded due to conflicts
154- pub fn txout ( & self , op : OutPoint ) -> Option < FullTxOut < P > > {
253+ pub fn txout ( & self , op : OutPoint ) -> Option < CanonicalTxOut < P > > {
155254 let ( tx, pos) = self . txs . get ( & op. txid ) ?;
156255 let vout: usize = op. vout . try_into ( ) . ok ( ) ?;
157256 let txout = tx. output . get ( vout) ?;
158257 let spent_by = self . spends . get ( & op) . map ( |spent_by_txid| {
159258 let ( _, spent_by_pos) = & self . txs [ spent_by_txid] ;
160259 ( spent_by_pos. clone ( ) , * spent_by_txid)
161260 } ) ;
162- Some ( FullTxOut {
261+ Some ( CanonicalTxOut {
163262 pos : pos. clone ( ) ,
164263 outpoint : op,
165264 txout : txout. clone ( ) ,
@@ -202,7 +301,7 @@ impl<A, P: Clone> Canonical<A, P> {
202301 /// Get a filtered list of outputs from the given outpoints.
203302 ///
204303 /// This method takes an iterator of `(identifier, outpoint)` pairs and returns an iterator
205- /// of `(identifier, full_txout )` pairs for outpoints that exist in the canonical set.
304+ /// of `(identifier, canonical_txout )` pairs for outpoints that exist in the canonical set.
206305 /// Non-existent outpoints are silently filtered out.
207306 ///
208307 /// The identifier type `O` is useful for tracking which outpoints correspond to which addresses
@@ -228,7 +327,7 @@ impl<A, P: Clone> Canonical<A, P> {
228327 pub fn filter_outpoints < ' v , O : Clone + ' v > (
229328 & ' v self ,
230329 outpoints : impl IntoIterator < Item = ( O , OutPoint ) > + ' v ,
231- ) -> impl Iterator < Item = ( O , FullTxOut < P > ) > + ' v {
330+ ) -> impl Iterator < Item = ( O , CanonicalTxOut < P > ) > + ' v {
232331 outpoints
233332 . into_iter ( )
234333 . filter_map ( |( op_i, op) | Some ( ( op_i, self . txout ( op) ?) ) )
@@ -259,7 +358,7 @@ impl<A, P: Clone> Canonical<A, P> {
259358 pub fn filter_unspent_outpoints < ' v , O : Clone + ' v > (
260359 & ' v self ,
261360 outpoints : impl IntoIterator < Item = ( O , OutPoint ) > + ' v ,
262- ) -> impl Iterator < Item = ( O , FullTxOut < P > ) > + ' v {
361+ ) -> impl Iterator < Item = ( O , CanonicalTxOut < P > ) > + ' v {
263362 self . filter_outpoints ( outpoints)
264363 . filter ( |( _, txo) | txo. spent_by . is_none ( ) )
265364 }
@@ -337,7 +436,7 @@ impl<A: Anchor> CanonicalView<A> {
337436 pub fn balance < ' v , O : Clone + ' v > (
338437 & ' v self ,
339438 outpoints : impl IntoIterator < Item = ( O , OutPoint ) > + ' v ,
340- mut trust_predicate : impl FnMut ( & O , & FullTxOut < ChainPosition < A > > ) -> bool ,
439+ mut trust_predicate : impl FnMut ( & O , & CanonicalTxOut < ChainPosition < A > > ) -> bool ,
341440 min_confirmations : u32 ,
342441 ) -> Balance {
343442 let mut immature = Amount :: ZERO ;
@@ -387,3 +486,14 @@ impl<A: Anchor> CanonicalView<A> {
387486 }
388487 }
389488}
489+
490+ impl < A : Anchor > CanonicalTxs < A > {
491+ /// Creates a [`CanonicalViewTask`] that resolves [`CanonicalReason`]s into [`ChainPosition`]s.
492+ ///
493+ /// This is the second phase of the canonicalization pipeline. The resulting task
494+ /// queries the chain to verify anchors for transitively anchored transactions and
495+ /// produces a [`CanonicalView`] with resolved chain positions.
496+ pub fn view_task < ' g > ( self , tx_graph : & ' g TxGraph < A > ) -> CanonicalViewTask < ' g , A > {
497+ CanonicalViewTask :: new ( tx_graph, self . tip , self . order , self . txs , self . spends )
498+ }
499+ }
0 commit comments