@@ -176,6 +176,7 @@ pub struct TxGraph<A = ConfirmationBlockTime> {
176
176
txs : HashMap < Txid , TxNodeInternal > ,
177
177
spends : BTreeMap < OutPoint , HashSet < Txid > > ,
178
178
anchors : HashMap < Txid , BTreeSet < A > > ,
179
+ first_seen : HashMap < Txid , u64 > ,
179
180
last_seen : HashMap < Txid , u64 > ,
180
181
last_evicted : HashMap < Txid , u64 > ,
181
182
@@ -194,6 +195,7 @@ impl<A> Default for TxGraph<A> {
194
195
txs : Default :: default ( ) ,
195
196
spends : Default :: default ( ) ,
196
197
anchors : Default :: default ( ) ,
198
+ first_seen : Default :: default ( ) ,
197
199
last_seen : Default :: default ( ) ,
198
200
last_evicted : Default :: default ( ) ,
199
201
txs_by_highest_conf_heights : Default :: default ( ) ,
@@ -213,6 +215,8 @@ pub struct TxNode<'a, T, A> {
213
215
pub tx : T ,
214
216
/// The blocks that the transaction is "anchored" in.
215
217
pub anchors : & ' a BTreeSet < A > ,
218
+ /// The first-seen unix timestamp of the transaction.
219
+ pub first_seen : Option < u64 > ,
216
220
/// The last-seen unix timestamp of the transaction as unconfirmed.
217
221
pub last_seen_unconfirmed : Option < u64 > ,
218
222
}
@@ -324,6 +328,7 @@ impl<A> TxGraph<A> {
324
328
txid,
325
329
tx : tx. clone ( ) ,
326
330
anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
331
+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
327
332
last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
328
333
} ) ,
329
334
TxNodeInternal :: Partial ( _) => None ,
@@ -359,6 +364,7 @@ impl<A> TxGraph<A> {
359
364
txid,
360
365
tx : tx. clone ( ) ,
361
366
anchors : self . anchors . get ( & txid) . unwrap_or ( & self . empty_anchors ) ,
367
+ first_seen : self . first_seen . get ( & txid) . copied ( ) ,
362
368
last_seen_unconfirmed : self . last_seen . get ( & txid) . copied ( ) ,
363
369
} ) ,
364
370
_ => None ,
@@ -719,8 +725,40 @@ impl<A: Anchor> TxGraph<A> {
719
725
720
726
/// Inserts the given `seen_at` for `txid` into [`TxGraph`].
721
727
///
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`.
723
729
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 > {
724
762
let mut old_last_seen = None ;
725
763
let is_changed = match self . last_seen . entry ( txid) {
726
764
hash_map:: Entry :: Occupied ( mut e) => {
@@ -814,6 +852,7 @@ impl<A: Anchor> TxGraph<A> {
814
852
. iter ( )
815
853
. flat_map ( |( txid, anchors) | anchors. iter ( ) . map ( |a| ( a. clone ( ) , * txid) ) )
816
854
. collect ( ) ,
855
+ first_seen : self . first_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
817
856
last_seen : self . last_seen . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
818
857
last_evicted : self . last_evicted . iter ( ) . map ( |( & k, & v) | ( k, v) ) . collect ( ) ,
819
858
}
@@ -894,8 +933,12 @@ impl<A: Anchor> TxGraph<A> {
894
933
CanonicalReason :: ObservedIn { observed_in, .. } => match observed_in {
895
934
ObservedIn :: Mempool ( last_seen) => ChainPosition :: Unconfirmed {
896
935
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 ,
897
941
} ,
898
- ObservedIn :: Block ( _) => ChainPosition :: Unconfirmed { last_seen : None } ,
899
942
} ,
900
943
} ;
901
944
Ok ( CanonicalTx {
@@ -1255,6 +1298,9 @@ pub struct ChangeSet<A = ()> {
1255
1298
/// Added timestamps of when a transaction is last evicted from the mempool.
1256
1299
#[ cfg_attr( feature = "serde" , serde( default ) ) ]
1257
1300
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 > ,
1258
1304
}
1259
1305
1260
1306
impl < A > Default for ChangeSet < A > {
@@ -1263,6 +1309,7 @@ impl<A> Default for ChangeSet<A> {
1263
1309
txs : Default :: default ( ) ,
1264
1310
txouts : Default :: default ( ) ,
1265
1311
anchors : Default :: default ( ) ,
1312
+ first_seen : Default :: default ( ) ,
1266
1313
last_seen : Default :: default ( ) ,
1267
1314
last_evicted : Default :: default ( ) ,
1268
1315
}
@@ -1311,6 +1358,18 @@ impl<A: Ord> Merge for ChangeSet<A> {
1311
1358
self . txouts . extend ( other. txouts ) ;
1312
1359
self . anchors . extend ( other. anchors ) ;
1313
1360
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
+
1314
1373
// last_seen timestamps should only increase
1315
1374
self . last_seen . extend (
1316
1375
other
@@ -1333,6 +1392,7 @@ impl<A: Ord> Merge for ChangeSet<A> {
1333
1392
self . txs . is_empty ( )
1334
1393
&& self . txouts . is_empty ( )
1335
1394
&& self . anchors . is_empty ( )
1395
+ && self . first_seen . is_empty ( )
1336
1396
&& self . last_seen . is_empty ( )
1337
1397
&& self . last_evicted . is_empty ( )
1338
1398
}
@@ -1353,6 +1413,7 @@ impl<A: Ord> ChangeSet<A> {
1353
1413
anchors : BTreeSet :: < ( A2 , Txid ) > :: from_iter (
1354
1414
self . anchors . into_iter ( ) . map ( |( a, txid) | ( f ( a) , txid) ) ,
1355
1415
) ,
1416
+ first_seen : self . first_seen ,
1356
1417
last_seen : self . last_seen ,
1357
1418
last_evicted : self . last_evicted ,
1358
1419
}
0 commit comments