@@ -176,6 +176,7 @@ pub struct TxGraph<A = ConfirmationBlockTime> {
176176 txs : HashMap < Txid , TxNodeInternal > ,
177177 spends : BTreeMap < OutPoint , HashSet < Txid > > ,
178178 anchors : HashMap < Txid , BTreeSet < A > > ,
179+ first_seen : HashMap < Txid , u64 > ,
179180 last_seen : HashMap < Txid , u64 > ,
180181 last_evicted : HashMap < Txid , u64 > ,
181182
@@ -194,6 +195,7 @@ impl<A> Default for TxGraph<A> {
194195 txs : Default :: default ( ) ,
195196 spends : Default :: default ( ) ,
196197 anchors : Default :: default ( ) ,
198+ first_seen : Default :: default ( ) ,
197199 last_seen : Default :: default ( ) ,
198200 last_evicted : Default :: default ( ) ,
199201 txs_by_highest_conf_heights : Default :: default ( ) ,
@@ -213,6 +215,8 @@ pub struct TxNode<'a, T, A> {
213215 pub tx : T ,
214216 /// The blocks that the transaction is "anchored" in.
215217 pub anchors : & ' a BTreeSet < A > ,
218+ /// The first-seen unix timestamp of the transaction.
219+ pub first_seen : Option < u64 > ,
216220 /// The last-seen unix timestamp of the transaction as unconfirmed.
217221 pub last_seen_unconfirmed : Option < u64 > ,
218222}
@@ -324,6 +328,7 @@ impl<A> TxGraph<A> {
324328 txid,
325329 tx : tx. clone ( ) ,
326330 anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
331+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
327332 last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
328333 } ) ,
329334 TxNodeInternal :: Partial ( _) => None ,
@@ -359,6 +364,7 @@ impl<A> TxGraph<A> {
359364 txid,
360365 tx : tx. clone ( ) ,
361366 anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
367+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
362368 last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
363369 } ) ,
364370 _ => None ,
@@ -719,8 +725,40 @@ impl<A: Anchor> TxGraph<A> {
719725
720726 /// Inserts the given `seen_at` for `txid` into [`TxGraph`].
721727 ///
722- /// Note that [`TxGraph`] only keeps track of the latest `seen_at`.
728+ /// Note that [`TxGraph`] keeps track of the latest `seen_at` and the earliest `seen_at`.
723729 pub fn insert_seen_at ( & mut self , txid : Txid , seen_at : u64 ) -> ChangeSet < A > {
730+ let mut changeset_first_seen = self . update_first_seen ( txid, seen_at) ;
731+ let changeset_last_seen = self . update_last_seen ( txid, seen_at) ;
732+ changeset_first_seen. merge ( changeset_last_seen) ;
733+ changeset_first_seen
734+ }
735+
736+ /// Updates `first_seen` given a new `seen_at`.
737+ fn update_first_seen ( & mut self , txid : Txid , seen_at : u64 ) -> ChangeSet < A > {
738+ let is_changed = match self . first_seen . entry ( txid) {
739+ hash_map:: Entry :: Occupied ( mut e) => {
740+ let first_seen = e. get_mut ( ) ;
741+ let change = * first_seen > seen_at;
742+ if change {
743+ * first_seen = seen_at;
744+ }
745+ change
746+ }
747+ hash_map:: Entry :: Vacant ( e) => {
748+ e. insert ( seen_at) ;
749+ true
750+ }
751+ } ;
752+
753+ let mut changeset = ChangeSet :: < A > :: default ( ) ;
754+ if is_changed {
755+ changeset. first_seen . insert ( txid, seen_at) ;
756+ }
757+ changeset
758+ }
759+
760+ /// Updates `last_seen` given a new `seen_at`.
761+ fn update_last_seen ( & mut self , txid : Txid , seen_at : u64 ) -> ChangeSet < A > {
724762 let mut old_last_seen = None ;
725763 let is_changed = match self . last_seen . entry ( txid) {
726764 hash_map:: Entry :: Occupied ( mut e) => {
@@ -814,6 +852,7 @@ impl<A: Anchor> TxGraph<A> {
814852 . iter ( )
815853 . flat_map ( |( txid, anchors) | anchors. iter ( ) . map ( |a| ( a. clone ( ) , * txid) ) )
816854 . collect ( ) ,
855+ first_seen : self . first_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
817856 last_seen : self . last_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
818857 last_evicted : self . last_evicted . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
819858 }
@@ -894,8 +933,12 @@ impl<A: Anchor> TxGraph<A> {
894933 CanonicalReason :: ObservedIn { observed_in, .. } => match observed_in {
895934 ObservedIn :: Mempool ( last_seen) => ChainPosition :: Unconfirmed {
896935 last_seen : Some ( last_seen) ,
936+ first_seen : tx_node. first_seen ,
937+ } ,
938+ ObservedIn :: Block ( _) => ChainPosition :: Unconfirmed {
939+ last_seen : None ,
940+ first_seen : tx_node. first_seen ,
897941 } ,
898- ObservedIn :: Block ( _) => ChainPosition :: Unconfirmed { last_seen : None } ,
899942 } ,
900943 } ;
901944 Ok ( CanonicalTx {
@@ -1255,6 +1298,9 @@ pub struct ChangeSet<A = ()> {
12551298 /// Added timestamps of when a transaction is last evicted from the mempool.
12561299 #[ cfg_attr( feature = "serde" , serde( default ) ) ]
12571300 pub last_evicted : BTreeMap < Txid , u64 > ,
1301+ /// Added first-seen unix timestamps of transactions.
1302+ #[ cfg_attr( feature = "serde" , serde( default ) ) ]
1303+ pub first_seen : BTreeMap < Txid , u64 > ,
12581304}
12591305
12601306impl < A > Default for ChangeSet < A > {
@@ -1263,6 +1309,7 @@ impl<A> Default for ChangeSet<A> {
12631309 txs : Default :: default ( ) ,
12641310 txouts : Default :: default ( ) ,
12651311 anchors : Default :: default ( ) ,
1312+ first_seen : Default :: default ( ) ,
12661313 last_seen : Default :: default ( ) ,
12671314 last_evicted : Default :: default ( ) ,
12681315 }
@@ -1311,6 +1358,18 @@ impl<A: Ord> Merge for ChangeSet<A> {
13111358 self . txouts . extend ( other. txouts ) ;
13121359 self . anchors . extend ( other. anchors ) ;
13131360
1361+ // first_seen timestamps should only decrease
1362+ self . first_seen . extend (
1363+ other
1364+ . first_seen
1365+ . into_iter ( )
1366+ . filter ( |( txid, update_fs) | match self . first_seen . get ( txid) {
1367+ Some ( existing) => update_fs < existing,
1368+ None => true ,
1369+ } )
1370+ . collect :: < Vec < _ > > ( ) ,
1371+ ) ;
1372+
13141373 // last_seen timestamps should only increase
13151374 self . last_seen . extend (
13161375 other
@@ -1333,6 +1392,7 @@ impl<A: Ord> Merge for ChangeSet<A> {
13331392 self . txs . is_empty ( )
13341393 && self . txouts . is_empty ( )
13351394 && self . anchors . is_empty ( )
1395+ && self . first_seen . is_empty ( )
13361396 && self . last_seen . is_empty ( )
13371397 && self . last_evicted . is_empty ( )
13381398 }
@@ -1353,6 +1413,7 @@ impl<A: Ord> ChangeSet<A> {
13531413 anchors : BTreeSet :: < ( A2 , Txid ) > :: from_iter (
13541414 self . anchors . into_iter ( ) . map ( |( a, txid) | ( f ( a) , txid) ) ,
13551415 ) ,
1416+ first_seen : self . first_seen ,
13561417 last_seen : self . last_seen ,
13571418 last_evicted : self . last_evicted ,
13581419 }
0 commit comments