@@ -6,49 +6,21 @@ use crate::{
66} ;
77
88/// Represents an observation of some chain data.
9+ ///
10+ /// The generic `A` should be a [`BlockAnchor`] implementation.
911#[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord , core:: hash:: Hash ) ]
10- pub enum ObservedIn < A > {
11- /// The chain data is seen in a block identified by `A`.
12- Block ( A ) ,
12+ pub enum ObservedAs < A > {
13+ /// The chain data is seen as confirmed, and in anchored by `A`.
14+ Confirmed ( A ) ,
1315 /// The chain data is seen in mempool at this given timestamp.
14- /// TODO: Call this `Unconfirmed`.
15- Mempool ( u64 ) ,
16+ Unconfirmed ( u64 ) ,
1617}
1718
18- impl < A : Clone > ObservedIn < & A > {
19- pub fn into_owned ( self ) -> ObservedIn < A > {
19+ impl < A : Clone > ObservedAs < & A > {
20+ pub fn cloned ( self ) -> ObservedAs < A > {
2021 match self {
21- ObservedIn :: Block ( a) => ObservedIn :: Block ( a. clone ( ) ) ,
22- ObservedIn :: Mempool ( last_seen) => ObservedIn :: Mempool ( last_seen) ,
23- }
24- }
25- }
26-
27- impl ChainPosition for ObservedIn < BlockId > {
28- fn height ( & self ) -> TxHeight {
29- match self {
30- ObservedIn :: Block ( block_id) => TxHeight :: Confirmed ( block_id. height ) ,
31- ObservedIn :: Mempool ( _) => TxHeight :: Unconfirmed ,
32- }
33- }
34-
35- fn max_ord_of_height ( height : TxHeight ) -> Self {
36- match height {
37- TxHeight :: Confirmed ( height) => ObservedIn :: Block ( BlockId {
38- height,
39- hash : Hash :: from_inner ( [ u8:: MAX ; 32 ] ) ,
40- } ) ,
41- TxHeight :: Unconfirmed => Self :: Mempool ( u64:: MAX ) ,
42- }
43- }
44-
45- fn min_ord_of_height ( height : TxHeight ) -> Self {
46- match height {
47- TxHeight :: Confirmed ( height) => ObservedIn :: Block ( BlockId {
48- height,
49- hash : Hash :: from_inner ( [ u8:: MIN ; 32 ] ) ,
50- } ) ,
51- TxHeight :: Unconfirmed => Self :: Mempool ( u64:: MIN ) ,
22+ ObservedAs :: Confirmed ( a) => ObservedAs :: Confirmed ( a. clone ( ) ) ,
23+ ObservedAs :: Unconfirmed ( last_seen) => ObservedAs :: Unconfirmed ( last_seen) ,
5224 }
5325 }
5426}
@@ -217,20 +189,20 @@ impl From<(&u32, &BlockHash)> for BlockId {
217189
218190/// A `TxOut` with as much data as we can retrieve about it
219191#[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
220- pub struct FullTxOut < I > {
192+ pub struct FullTxOut < P > {
221193 /// The location of the `TxOut`.
222194 pub outpoint : OutPoint ,
223195 /// The `TxOut`.
224196 pub txout : TxOut ,
225197 /// The position of the transaction in `outpoint` in the overall chain.
226- pub chain_position : I ,
198+ pub chain_position : P ,
227199 /// The txid and chain position of the transaction (if any) that has spent this output.
228- pub spent_by : Option < ( I , Txid ) > ,
200+ pub spent_by : Option < ( P , Txid ) > ,
229201 /// Whether this output is on a coinbase transaction.
230202 pub is_on_coinbase : bool ,
231203}
232204
233- impl < I : ChainPosition > FullTxOut < I > {
205+ impl < P : ChainPosition > FullTxOut < P > {
234206 /// Whether the utxo is/was/will be spendable at `height`.
235207 ///
236208 /// It is spendable if it is not an immature coinbase output and no spending tx has been
@@ -269,15 +241,63 @@ impl<I: ChainPosition> FullTxOut<I> {
269241 }
270242}
271243
272- impl < A : Clone > FullTxOut < ObservedIn < & A > > {
273- pub fn into_owned ( self ) -> FullTxOut < ObservedIn < A > > {
274- FullTxOut {
275- outpoint : self . outpoint ,
276- txout : self . txout ,
277- chain_position : self . chain_position . into_owned ( ) ,
278- spent_by : self . spent_by . map ( |( o, txid) | ( o. into_owned ( ) , txid) ) ,
279- is_on_coinbase : self . is_on_coinbase ,
244+ impl < A : BlockAnchor > FullTxOut < ObservedAs < A > > {
245+ /// Whether the `txout` is considered mature.
246+ ///
247+ /// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
248+ /// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
249+ ///
250+ /// [`is_mature`]: Self::is_mature
251+ pub fn is_observed_as_mature ( & self , tip : u32 ) -> bool {
252+ if !self . is_on_coinbase {
253+ return false ;
280254 }
255+
256+ let tx_height = match & self . chain_position {
257+ ObservedAs :: Confirmed ( anchor) => anchor. anchor_block ( ) . height ,
258+ ObservedAs :: Unconfirmed ( _) => {
259+ debug_assert ! ( false , "coinbase tx can never be unconfirmed" ) ;
260+ return false ;
261+ }
262+ } ;
263+
264+ let age = tip. saturating_sub ( tx_height) ;
265+ if age + 1 < COINBASE_MATURITY {
266+ return false ;
267+ }
268+
269+ true
270+ }
271+
272+ /// Whether the utxo is/was/will be spendable with chain `tip`.
273+ ///
274+ /// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
275+ /// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
276+ ///
277+ /// [`is_spendable_at`]: Self::is_spendable_at
278+ pub fn is_observed_as_spendable ( & self , tip : u32 ) -> bool {
279+ if !self . is_observed_as_mature ( tip) {
280+ return false ;
281+ }
282+
283+ match & self . chain_position {
284+ ObservedAs :: Confirmed ( anchor) => {
285+ if anchor. anchor_block ( ) . height > tip {
286+ return false ;
287+ }
288+ }
289+ // [TODO] Why are unconfirmed txs always considered unspendable here?
290+ ObservedAs :: Unconfirmed ( _) => return false ,
291+ } ;
292+
293+ // if the spending tx is confirmed within tip height, the txout is no longer spendable
294+ if let Some ( ( ObservedAs :: Confirmed ( spending_anchor) , _) ) = & self . spent_by {
295+ if spending_anchor. anchor_block ( ) . height <= tip {
296+ return false ;
297+ }
298+ }
299+
300+ true
281301 }
282302}
283303
0 commit comments