@@ -177,6 +177,7 @@ pub struct TxGraph<A = ConfirmationBlockTime> {
177
177
txs : HashMap < Txid , TxNodeInternal > ,
178
178
spends : BTreeMap < OutPoint , HashSet < Txid > > ,
179
179
anchors : HashMap < Txid , BTreeSet < A > > ,
180
+ first_seen : HashMap < Txid , u64 > ,
180
181
last_seen : HashMap < Txid , u64 > ,
181
182
last_evicted : HashMap < Txid , u64 > ,
182
183
@@ -195,6 +196,7 @@ impl<A> Default for TxGraph<A> {
195
196
txs : Default :: default ( ) ,
196
197
spends : Default :: default ( ) ,
197
198
anchors : Default :: default ( ) ,
199
+ first_seen : Default :: default ( ) ,
198
200
last_seen : Default :: default ( ) ,
199
201
last_evicted : Default :: default ( ) ,
200
202
txs_by_highest_conf_heights : Default :: default ( ) ,
@@ -214,6 +216,8 @@ pub struct TxNode<'a, T, A> {
214
216
pub tx : T ,
215
217
/// The blocks that the transaction is "anchored" in.
216
218
pub anchors : & ' a BTreeSet < A > ,
219
+ /// The first-seen unix timestamp of the transaction.
220
+ pub first_seen : Option < u64 > ,
217
221
/// The last-seen unix timestamp of the transaction as unconfirmed.
218
222
pub last_seen_unconfirmed : Option < u64 > ,
219
223
}
@@ -337,6 +341,7 @@ impl<A> TxGraph<A> {
337
341
txid,
338
342
tx : tx. clone ( ) ,
339
343
anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
344
+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
340
345
last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
341
346
} ) ,
342
347
TxNodeInternal :: Partial ( _) => None ,
@@ -372,6 +377,7 @@ impl<A> TxGraph<A> {
372
377
txid,
373
378
tx : tx. clone ( ) ,
374
379
anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
380
+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
375
381
last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
376
382
} ) ,
377
383
_ => None ,
@@ -789,8 +795,40 @@ impl<A: Anchor> TxGraph<A> {
789
795
790
796
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
791
797
///
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`.
793
799
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 > {
794
832
let mut old_last_seen = None ;
795
833
let is_changed = match self . last_seen . entry ( txid) {
796
834
hash_map:: Entry :: Occupied ( mut e) => {
@@ -904,6 +942,7 @@ impl<A: Anchor> TxGraph<A> {
904
942
. iter ( )
905
943
. flat_map ( |( txid, anchors) | anchors. iter ( ) . map ( |a| ( a. clone ( ) , * txid) ) )
906
944
. collect ( ) ,
945
+ first_seen : self . first_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
907
946
last_seen : self . last_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
908
947
last_evicted : self . last_evicted . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
909
948
}
@@ -978,10 +1017,12 @@ impl<A: Anchor> TxGraph<A> {
978
1017
transitively : None ,
979
1018
} ,
980
1019
None => ChainPosition :: Unconfirmed {
1020
+ first_seen : tx_node. first_seen ,
981
1021
last_seen : tx_node. last_seen_unconfirmed ,
982
1022
} ,
983
1023
} ,
984
1024
None => ChainPosition :: Unconfirmed {
1025
+ first_seen : tx_node. first_seen ,
985
1026
last_seen : tx_node. last_seen_unconfirmed ,
986
1027
} ,
987
1028
} ,
@@ -1003,9 +1044,13 @@ impl<A: Anchor> TxGraph<A> {
1003
1044
} ,
1004
1045
CanonicalReason :: ObservedIn { observed_in, .. } => match observed_in {
1005
1046
ObservedIn :: Mempool ( last_seen) => ChainPosition :: Unconfirmed {
1047
+ first_seen : tx_node. first_seen ,
1006
1048
last_seen : Some ( last_seen) ,
1007
1049
} ,
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
+ } ,
1009
1054
} ,
1010
1055
} ;
1011
1056
Ok ( CanonicalTx {
@@ -1372,6 +1417,9 @@ pub struct ChangeSet<A = ()> {
1372
1417
/// Added timestamps of when a transaction is last evicted from the mempool.
1373
1418
#[ cfg_attr( feature = "serde" , serde( default ) ) ]
1374
1419
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 > ,
1375
1423
}
1376
1424
1377
1425
impl < A > Default for ChangeSet < A > {
@@ -1380,6 +1428,7 @@ impl<A> Default for ChangeSet<A> {
1380
1428
txs : Default :: default ( ) ,
1381
1429
txouts : Default :: default ( ) ,
1382
1430
anchors : Default :: default ( ) ,
1431
+ first_seen : Default :: default ( ) ,
1383
1432
last_seen : Default :: default ( ) ,
1384
1433
last_evicted : Default :: default ( ) ,
1385
1434
}
@@ -1428,6 +1477,18 @@ impl<A: Ord> Merge for ChangeSet<A> {
1428
1477
self . txouts . extend ( other. txouts ) ;
1429
1478
self . anchors . extend ( other. anchors ) ;
1430
1479
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
+
1431
1492
// last_seen timestamps should only increase
1432
1493
self . last_seen . extend (
1433
1494
other
@@ -1450,6 +1511,7 @@ impl<A: Ord> Merge for ChangeSet<A> {
1450
1511
self . txs . is_empty ( )
1451
1512
&& self . txouts . is_empty ( )
1452
1513
&& self . anchors . is_empty ( )
1514
+ && self . first_seen . is_empty ( )
1453
1515
&& self . last_seen . is_empty ( )
1454
1516
&& self . last_evicted . is_empty ( )
1455
1517
}
@@ -1470,6 +1532,7 @@ impl<A: Ord> ChangeSet<A> {
1470
1532
anchors : BTreeSet :: < ( A2 , Txid ) > :: from_iter (
1471
1533
self . anchors . into_iter ( ) . map ( |( a, txid) | ( f ( a) , txid) ) ,
1472
1534
) ,
1535
+ first_seen : self . first_seen ,
1473
1536
last_seen : self . last_seen ,
1474
1537
last_evicted : self . last_evicted ,
1475
1538
}
0 commit comments