@@ -5,7 +5,7 @@ use crate::Anchor;
55/// Represents the observed position of some chain data.
66///
77/// The generic `A` should be a [`Anchor`] implementation.
8- #[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord , core:: hash:: Hash ) ]
8+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , core:: hash:: Hash ) ]
99#[ cfg_attr(
1010 feature = "serde" ,
1111 derive( serde:: Deserialize , serde:: Serialize ) ,
@@ -85,8 +85,86 @@ impl<A: Anchor> ChainPosition<A> {
8585 }
8686}
8787
88+ /// Ordering for `ChainPosition`:
89+ ///
90+ /// 1. Confirmed transactions come before unconfirmed
91+ /// 2. Confirmed transactions are ordered by anchor (lower height = earlier)
92+ /// 3. At equal anchor height, transitive confirmations come before direct
93+ /// 4. Unconfirmed transactions with mempool timestamps come before those without
94+ /// 5. Unconfirmed transactions are ordered by `first_seen`, then `last_seen`
95+ impl < A : Ord > Ord for ChainPosition < A > {
96+ fn cmp ( & self , other : & Self ) -> core:: cmp:: Ordering {
97+ use core:: cmp:: Ordering ;
98+
99+ match ( self , other) {
100+ // Both confirmed: compare by anchor first
101+ (
102+ ChainPosition :: Confirmed {
103+ anchor : a1,
104+ transitively : t1,
105+ } ,
106+ ChainPosition :: Confirmed {
107+ anchor : a2,
108+ transitively : t2,
109+ } ,
110+ ) => {
111+ // First compare anchors
112+ match a1. cmp ( a2) {
113+ Ordering :: Equal => {
114+ // Same anchor: transitive before direct (may be earlier)
115+ match ( t1, t2) {
116+ ( None , None ) => Ordering :: Equal ,
117+ ( None , Some ( _) ) => Ordering :: Greater , // Direct comes after transitive
118+ ( Some ( _) , None ) => Ordering :: Less , // Transitive comes before direct
119+ // Both transitive: txid tiebreaker
120+ ( Some ( tx1) , Some ( tx2) ) => tx1. cmp ( tx2) ,
121+ }
122+ }
123+ other => other,
124+ }
125+ }
126+
127+ // Both unconfirmed: special handling for None values
128+ (
129+ ChainPosition :: Unconfirmed {
130+ first_seen : f1,
131+ last_seen : l1,
132+ } ,
133+ ChainPosition :: Unconfirmed {
134+ first_seen : f2,
135+ last_seen : l2,
136+ } ,
137+ ) => {
138+ // Never-in-mempool (None, None) ordered last
139+ match ( f1. or ( * l1) , f2. or ( * l2) ) {
140+ ( None , None ) => Ordering :: Equal ,
141+ ( None , Some ( _) ) => Ordering :: Greater , // Never-seen after seen
142+ ( Some ( _) , None ) => Ordering :: Less , // Seen before never-seen
143+ ( Some ( _) , Some ( _) ) => {
144+ // Both seen: compare first_seen, then last_seen
145+ f1. cmp ( f2) . then_with ( || l1. cmp ( l2) )
146+ }
147+ }
148+ }
149+
150+ // Confirmed always comes before unconfirmed
151+ ( ChainPosition :: Confirmed { .. } , ChainPosition :: Unconfirmed { .. } ) => Ordering :: Less ,
152+ ( ChainPosition :: Unconfirmed { .. } , ChainPosition :: Confirmed { .. } ) => {
153+ Ordering :: Greater
154+ }
155+ }
156+ }
157+ }
158+
159+ /// Partial ordering for `ChainPosition` - delegates to `Ord` implementation.
160+ impl < A : Ord > PartialOrd for ChainPosition < A > {
161+ fn partial_cmp ( & self , other : & Self ) -> Option < core:: cmp:: Ordering > {
162+ Some ( self . cmp ( other) )
163+ }
164+ }
165+
88166/// A `TxOut` with as much data as we can retrieve about it
89- #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
167+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
90168pub struct FullTxOut < A > {
91169 /// The position of the transaction in `outpoint` in the overall chain.
92170 pub chain_position : ChainPosition < A > ,
@@ -100,6 +178,22 @@ pub struct FullTxOut<A> {
100178 pub is_on_coinbase : bool ,
101179}
102180
181+ impl < A : Ord > Ord for FullTxOut < A > {
182+ fn cmp ( & self , other : & Self ) -> core:: cmp:: Ordering {
183+ self . chain_position
184+ . cmp ( & other. chain_position )
185+ // Tie-break with `outpoint` and `spent_by`.
186+ . then_with ( || self . outpoint . cmp ( & other. outpoint ) )
187+ . then_with ( || self . spent_by . cmp ( & other. spent_by ) )
188+ }
189+ }
190+
191+ impl < A : Ord > PartialOrd for FullTxOut < A > {
192+ fn partial_cmp ( & self , other : & Self ) -> Option < core:: cmp:: Ordering > {
193+ Some ( self . cmp ( other) )
194+ }
195+ }
196+
103197impl < A : Anchor > FullTxOut < A > {
104198 /// Whether the `txout` is considered mature.
105199 ///
@@ -167,22 +261,16 @@ impl<A: Anchor> FullTxOut<A> {
167261#[ cfg_attr( coverage_nightly, coverage( off) ) ]
168262mod test {
169263 use bdk_core:: ConfirmationBlockTime ;
264+ use bitcoin:: hashes:: Hash ;
170265
171266 use crate :: BlockId ;
172267
173268 use super :: * ;
174269
175270 #[ test]
176271 fn chain_position_ord ( ) {
177- let unconf1 = ChainPosition :: < ConfirmationBlockTime > :: Unconfirmed {
178- last_seen : Some ( 10 ) ,
179- first_seen : Some ( 10 ) ,
180- } ;
181- let unconf2 = ChainPosition :: < ConfirmationBlockTime > :: Unconfirmed {
182- last_seen : Some ( 20 ) ,
183- first_seen : Some ( 20 ) ,
184- } ;
185- let conf1 = ChainPosition :: Confirmed {
272+ // Create test positions
273+ let conf_deep = ChainPosition :: Confirmed {
186274 anchor : ConfirmationBlockTime {
187275 confirmation_time : 20 ,
188276 block_id : BlockId {
@@ -192,7 +280,17 @@ mod test {
192280 } ,
193281 transitively : None ,
194282 } ;
195- let conf2 = ChainPosition :: Confirmed {
283+ let conf_deep_transitive = ChainPosition :: Confirmed {
284+ anchor : ConfirmationBlockTime {
285+ confirmation_time : 20 ,
286+ block_id : BlockId {
287+ height : 9 ,
288+ ..Default :: default ( )
289+ } ,
290+ } ,
291+ transitively : Some ( Txid :: all_zeros ( ) ) ,
292+ } ;
293+ let conf_shallow = ChainPosition :: Confirmed {
196294 anchor : ConfirmationBlockTime {
197295 confirmation_time : 15 ,
198296 block_id : BlockId {
@@ -202,12 +300,78 @@ mod test {
202300 } ,
203301 transitively : None ,
204302 } ;
303+ let unconf_seen_early = ChainPosition :: < ConfirmationBlockTime > :: Unconfirmed {
304+ first_seen : Some ( 10 ) ,
305+ last_seen : Some ( 10 ) ,
306+ } ;
307+ let unconf_seen_late = ChainPosition :: < ConfirmationBlockTime > :: Unconfirmed {
308+ first_seen : Some ( 20 ) ,
309+ last_seen : Some ( 20 ) ,
310+ } ;
311+ let unconf_never_seen = ChainPosition :: < ConfirmationBlockTime > :: Unconfirmed {
312+ first_seen : None ,
313+ last_seen : None ,
314+ } ;
315+
316+ // Test ordering: confirmed < unconfirmed
317+ assert ! (
318+ conf_deep < unconf_seen_early,
319+ "confirmed comes before unconfirmed"
320+ ) ;
321+ assert ! (
322+ conf_shallow < unconf_seen_early,
323+ "confirmed comes before unconfirmed"
324+ ) ;
205325
206- assert ! ( unconf2 > unconf1, "higher last_seen means higher ord" ) ;
207- assert ! ( unconf1 > conf1, "unconfirmed is higher ord than confirmed" ) ;
326+ // Test ordering within confirmed: lower height (more confirmations) comes first
208327 assert ! (
209- conf2 > conf1,
210- "confirmation_height is higher then it should be higher ord"
328+ conf_deep < conf_shallow,
329+ "deeper blocks (lower height) come first"
330+ ) ;
331+
332+ // Test ordering within confirmed at same height: transitive comes before direct
333+ assert ! (
334+ conf_deep_transitive < conf_deep,
335+ "transitive confirmation comes before direct at same height"
336+ ) ;
337+
338+ // Test ordering within unconfirmed: earlier first_seen comes first
339+ assert ! (
340+ unconf_seen_early < unconf_seen_late,
341+ "earlier first_seen comes first"
342+ ) ;
343+
344+ // Test ordering: never seen in mempool comes last
345+ assert ! (
346+ unconf_seen_early < unconf_never_seen,
347+ "seen in mempool comes before never seen"
348+ ) ;
349+ assert ! (
350+ unconf_seen_late < unconf_never_seen,
351+ "seen in mempool comes before never seen"
352+ ) ;
353+
354+ // Full ordering test: most confirmed -> least confirmed -> unconfirmed seen -> unconfirmed
355+ // never seen
356+ let mut positions = vec ! [
357+ unconf_never_seen,
358+ unconf_seen_late,
359+ conf_shallow,
360+ unconf_seen_early,
361+ conf_deep,
362+ conf_deep_transitive,
363+ ] ;
364+ positions. sort ( ) ;
365+ assert_eq ! (
366+ positions,
367+ vec![
368+ conf_deep_transitive, // Most confirmed (potentially)
369+ conf_deep, // Deep confirmation
370+ conf_shallow, // Shallow confirmation
371+ unconf_seen_early, // Unconfirmed, seen early
372+ unconf_seen_late, // Unconfirmed, seen late
373+ unconf_never_seen, // Never in mempool
374+ ]
211375 ) ;
212376 }
213377
0 commit comments