@@ -177,6 +177,7 @@ pub struct TxGraph<A = ConfirmationBlockTime> {
177177 txs : HashMap < Txid , TxNodeInternal > ,
178178 spends : BTreeMap < OutPoint , HashSet < Txid > > ,
179179 anchors : HashMap < Txid , BTreeSet < A > > ,
180+ first_seen : HashMap < Txid , u64 > ,
180181 last_seen : HashMap < Txid , u64 > ,
181182 last_evicted : HashMap < Txid , u64 > ,
182183
@@ -195,6 +196,7 @@ impl<A> Default for TxGraph<A> {
195196 txs : Default :: default ( ) ,
196197 spends : Default :: default ( ) ,
197198 anchors : Default :: default ( ) ,
199+ first_seen : Default :: default ( ) ,
198200 last_seen : Default :: default ( ) ,
199201 last_evicted : Default :: default ( ) ,
200202 txs_by_highest_conf_heights : Default :: default ( ) ,
@@ -214,6 +216,8 @@ pub struct TxNode<'a, T, A> {
214216 pub tx : T ,
215217 /// The blocks that the transaction is "anchored" in.
216218 pub anchors : & ' a BTreeSet < A > ,
219+ /// The first-seen unix timestamp of the transaction.
220+ pub first_seen : Option < u64 > ,
217221 /// The last-seen unix timestamp of the transaction as unconfirmed.
218222 pub last_seen_unconfirmed : Option < u64 > ,
219223}
@@ -337,6 +341,7 @@ impl<A> TxGraph<A> {
337341 txid,
338342 tx : tx. clone ( ) ,
339343 anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
344+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
340345 last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
341346 } ) ,
342347 TxNodeInternal :: Partial ( _) => None ,
@@ -372,6 +377,7 @@ impl<A> TxGraph<A> {
372377 txid,
373378 tx : tx. clone ( ) ,
374379 anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
380+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
375381 last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
376382 } ) ,
377383 _ => None ,
@@ -789,8 +795,40 @@ impl<A: Anchor> TxGraph<A> {
789795
790796 /// Inserts the given `seen_at` for `txid` into [`TxGraph`].
791797 ///
792- /// Note that [`TxGraph`] only keeps track of the latest `seen_at`.
798+ /// Note that [`TxGraph`] keeps track of the latest `seen_at` and the earliest `seen_at`.
793799 pub fn insert_seen_at ( & mut self , txid : Txid , seen_at : u64 ) -> ChangeSet < A > {
800+ let mut changeset_first_seen = self . update_first_seen ( txid, seen_at) ;
801+ let changeset_last_seen = self . update_last_seen ( txid, seen_at) ;
802+ changeset_first_seen. merge ( changeset_last_seen) ;
803+ changeset_first_seen
804+ }
805+
806+ /// Updates `first_seen` given a new `seen_at`.
807+ fn update_first_seen ( & mut self , txid : Txid , seen_at : u64 ) -> ChangeSet < A > {
808+ let is_changed = match self . first_seen . entry ( txid) {
809+ hash_map:: Entry :: Occupied ( mut e) => {
810+ let first_seen = e. get_mut ( ) ;
811+ let change = * first_seen > seen_at;
812+ if change {
813+ * first_seen = seen_at;
814+ }
815+ change
816+ }
817+ hash_map:: Entry :: Vacant ( e) => {
818+ e. insert ( seen_at) ;
819+ true
820+ }
821+ } ;
822+
823+ let mut changeset = ChangeSet :: < A > :: default ( ) ;
824+ if is_changed {
825+ changeset. first_seen . insert ( txid, seen_at) ;
826+ }
827+ changeset
828+ }
829+
830+ /// Updates `last_seen` given a new `seen_at`.
831+ fn update_last_seen ( & mut self , txid : Txid , seen_at : u64 ) -> ChangeSet < A > {
794832 let mut old_last_seen = None ;
795833 let is_changed = match self . last_seen . entry ( txid) {
796834 hash_map:: Entry :: Occupied ( mut e) => {
@@ -904,6 +942,7 @@ impl<A: Anchor> TxGraph<A> {
904942 . iter ( )
905943 . flat_map ( |( txid, anchors) | anchors. iter ( ) . map ( |a| ( a. clone ( ) , * txid) ) )
906944 . collect ( ) ,
945+ first_seen : self . first_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
907946 last_seen : self . last_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
908947 last_evicted : self . last_evicted . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
909948 }
@@ -978,10 +1017,12 @@ impl<A: Anchor> TxGraph<A> {
9781017 transitively : None ,
9791018 } ,
9801019 None => ChainPosition :: Unconfirmed {
1020+ first_seen : tx_node. first_seen ,
9811021 last_seen : tx_node. last_seen_unconfirmed ,
9821022 } ,
9831023 } ,
9841024 None => ChainPosition :: Unconfirmed {
1025+ first_seen : tx_node. first_seen ,
9851026 last_seen : tx_node. last_seen_unconfirmed ,
9861027 } ,
9871028 } ,
@@ -1003,9 +1044,13 @@ impl<A: Anchor> TxGraph<A> {
10031044 } ,
10041045 CanonicalReason :: ObservedIn { observed_in, .. } => match observed_in {
10051046 ObservedIn :: Mempool ( last_seen) => ChainPosition :: Unconfirmed {
1047+ first_seen : tx_node. first_seen ,
10061048 last_seen : Some ( last_seen) ,
10071049 } ,
1008- ObservedIn :: Block ( _) => ChainPosition :: Unconfirmed { last_seen : None } ,
1050+ ObservedIn :: Block ( _) => ChainPosition :: Unconfirmed {
1051+ first_seen : tx_node. first_seen ,
1052+ last_seen : None ,
1053+ } ,
10091054 } ,
10101055 } ;
10111056 Ok ( CanonicalTx {
@@ -1372,6 +1417,9 @@ pub struct ChangeSet<A = ()> {
13721417 /// Added timestamps of when a transaction is last evicted from the mempool.
13731418 #[ cfg_attr( feature = "serde" , serde( default ) ) ]
13741419 pub last_evicted : BTreeMap < Txid , u64 > ,
1420+ /// Added first-seen unix timestamps of transactions.
1421+ #[ cfg_attr( feature = "serde" , serde( default ) ) ]
1422+ pub first_seen : BTreeMap < Txid , u64 > ,
13751423}
13761424
13771425impl < A > Default for ChangeSet < A > {
@@ -1380,6 +1428,7 @@ impl<A> Default for ChangeSet<A> {
13801428 txs : Default :: default ( ) ,
13811429 txouts : Default :: default ( ) ,
13821430 anchors : Default :: default ( ) ,
1431+ first_seen : Default :: default ( ) ,
13831432 last_seen : Default :: default ( ) ,
13841433 last_evicted : Default :: default ( ) ,
13851434 }
@@ -1428,6 +1477,18 @@ impl<A: Ord> Merge for ChangeSet<A> {
14281477 self . txouts . extend ( other. txouts ) ;
14291478 self . anchors . extend ( other. anchors ) ;
14301479
1480+ // first_seen timestamps should only decrease
1481+ self . first_seen . extend (
1482+ other
1483+ . first_seen
1484+ . into_iter ( )
1485+ . filter ( |( txid, update_fs) | match self . first_seen . get ( txid) {
1486+ Some ( existing) => update_fs < existing,
1487+ None => true ,
1488+ } )
1489+ . collect :: < Vec < _ > > ( ) ,
1490+ ) ;
1491+
14311492 // last_seen timestamps should only increase
14321493 self . last_seen . extend (
14331494 other
@@ -1450,6 +1511,7 @@ impl<A: Ord> Merge for ChangeSet<A> {
14501511 self . txs . is_empty ( )
14511512 && self . txouts . is_empty ( )
14521513 && self . anchors . is_empty ( )
1514+ && self . first_seen . is_empty ( )
14531515 && self . last_seen . is_empty ( )
14541516 && self . last_evicted . is_empty ( )
14551517 }
@@ -1470,6 +1532,7 @@ impl<A: Ord> ChangeSet<A> {
14701532 anchors : BTreeSet :: < ( A2 , Txid ) > :: from_iter (
14711533 self . anchors . into_iter ( ) . map ( |( a, txid) | ( f ( a) , txid) ) ,
14721534 ) ,
1535+ first_seen : self . first_seen ,
14731536 last_seen : self . last_seen ,
14741537 last_evicted : self . last_evicted ,
14751538 }
0 commit comments