@@ -29,18 +29,19 @@ const (
2929// Datastore wraps the datastore client, stores the sensorID, and other
3030// information needed when writing blocks and transactions.
3131type Datastore struct {
32- client * datastore.Client
33- sensorID string
34- chainID * big.Int
35- maxConcurrency int
36- shouldWriteBlocks bool
37- shouldWriteBlockEvents bool
38- shouldWriteFirstBlockEvent bool
39- shouldWriteTransactions bool
40- shouldWriteTransactionEvents bool
41- shouldWritePeers bool
42- jobs chan struct {}
43- ttl time.Duration
32+ client * datastore.Client
33+ sensorID string
34+ chainID * big.Int
35+ maxConcurrency int
36+ shouldWriteBlocks bool
37+ shouldWriteBlockEvents bool
38+ shouldWriteFirstBlockEvent bool
39+ shouldWriteTransactions bool
40+ shouldWriteTransactionEvents bool
41+ shouldWriteFirstTransactionEvent bool
42+ shouldWritePeers bool
43+ jobs chan struct {}
44+ ttl time.Duration
4445}
4546
4647// DatastoreEvent can represent a peer sending the sensor a transaction hash or
@@ -94,20 +95,21 @@ type DatastoreBlock struct {
9495// not indexed because there is a max sized for indexed byte slices, which Data
9596// will occasionally exceed.
9697type DatastoreTransaction struct {
97- Data []byte `datastore:",noindex"`
98- From string
99- Gas string
100- GasFeeCap string
101- GasPrice string
102- GasTipCap string
103- Nonce string
104- To string
105- Value string
106- V , R , S string `datastore:",noindex"`
107- Time time.Time
108- TimeFirstSeen time.Time
109- TTL time.Time
110- Type int16
98+ Data []byte `datastore:",noindex"`
99+ From string
100+ Gas string
101+ GasFeeCap string
102+ GasPrice string
103+ GasTipCap string
104+ Nonce string
105+ To string
106+ Value string
107+ V , R , S string `datastore:",noindex"`
108+ Time time.Time
109+ TimeFirstSeen time.Time
110+ TTL time.Time
111+ Type int16
112+ SensorFirstSeen string
111113}
112114
113115type DatastorePeer struct {
@@ -121,18 +123,19 @@ type DatastorePeer struct {
121123
122124// DatastoreOptions is used when creating a NewDatastore.
123125type DatastoreOptions struct {
124- ProjectID string
125- DatabaseID string
126- SensorID string
127- ChainID uint64
128- MaxConcurrency int
129- ShouldWriteBlocks bool
130- ShouldWriteBlockEvents bool
131- ShouldWriteFirstBlockEvent bool
132- ShouldWriteTransactions bool
133- ShouldWriteTransactionEvents bool
134- ShouldWritePeers bool
135- TTL time.Duration
126+ ProjectID string
127+ DatabaseID string
128+ SensorID string
129+ ChainID uint64
130+ MaxConcurrency int
131+ ShouldWriteBlocks bool
132+ ShouldWriteBlockEvents bool
133+ ShouldWriteFirstBlockEvent bool
134+ ShouldWriteTransactions bool
135+ ShouldWriteTransactionEvents bool
136+ ShouldWriteFirstTransactionEvent bool
137+ ShouldWritePeers bool
138+ TTL time.Duration
136139}
137140
138141// NewDatastore connects to datastore and creates the client. This should
@@ -144,18 +147,19 @@ func NewDatastore(ctx context.Context, opts DatastoreOptions) Database {
144147 }
145148
146149 return & Datastore {
147- client : client ,
148- sensorID : opts .SensorID ,
149- chainID : new (big.Int ).SetUint64 (opts .ChainID ),
150- maxConcurrency : opts .MaxConcurrency ,
151- shouldWriteBlocks : opts .ShouldWriteBlocks ,
152- shouldWriteBlockEvents : opts .ShouldWriteBlockEvents ,
153- shouldWriteFirstBlockEvent : opts .ShouldWriteFirstBlockEvent ,
154- shouldWriteTransactions : opts .ShouldWriteTransactions ,
155- shouldWriteTransactionEvents : opts .ShouldWriteTransactionEvents ,
156- shouldWritePeers : opts .ShouldWritePeers ,
157- jobs : make (chan struct {}, opts .MaxConcurrency ),
158- ttl : opts .TTL ,
150+ client : client ,
151+ sensorID : opts .SensorID ,
152+ chainID : new (big.Int ).SetUint64 (opts .ChainID ),
153+ maxConcurrency : opts .MaxConcurrency ,
154+ shouldWriteBlocks : opts .ShouldWriteBlocks ,
155+ shouldWriteBlockEvents : opts .ShouldWriteBlockEvents ,
156+ shouldWriteFirstBlockEvent : opts .ShouldWriteFirstBlockEvent ,
157+ shouldWriteTransactions : opts .ShouldWriteTransactions ,
158+ shouldWriteTransactionEvents : opts .ShouldWriteTransactionEvents ,
159+ shouldWriteFirstTransactionEvent : opts .ShouldWriteFirstTransactionEvent ,
160+ shouldWritePeers : opts .ShouldWritePeers ,
161+ jobs : make (chan struct {}, opts .MaxConcurrency ),
162+ ttl : opts .TTL ,
159163 }
160164}
161165
@@ -432,22 +436,23 @@ func (d *Datastore) newDatastoreTransaction(tx *types.Transaction, tfs time.Time
432436 }
433437
434438 return & DatastoreTransaction {
435- Data : tx .Data (),
436- From : from ,
437- Gas : fmt .Sprint (tx .Gas ()),
438- GasFeeCap : tx .GasFeeCap ().String (),
439- GasPrice : tx .GasPrice ().String (),
440- GasTipCap : tx .GasTipCap ().String (),
441- Nonce : fmt .Sprint (tx .Nonce ()),
442- To : to ,
443- Value : tx .Value ().String (),
444- V : v .String (),
445- R : r .String (),
446- S : s .String (),
447- Time : tx .Time (),
448- TimeFirstSeen : tfs ,
449- TTL : tfs .Add (d .ttl ),
450- Type : int16 (tx .Type ()),
439+ Data : tx .Data (),
440+ From : from ,
441+ Gas : fmt .Sprint (tx .Gas ()),
442+ GasFeeCap : tx .GasFeeCap ().String (),
443+ GasPrice : tx .GasPrice ().String (),
444+ GasTipCap : tx .GasTipCap ().String (),
445+ Nonce : fmt .Sprint (tx .Nonce ()),
446+ To : to ,
447+ Value : tx .Value ().String (),
448+ V : v .String (),
449+ R : r .String (),
450+ S : s .String (),
451+ Time : tx .Time (),
452+ TimeFirstSeen : tfs ,
453+ TTL : tfs .Add (d .ttl ),
454+ Type : int16 (tx .Type ()),
455+ SensorFirstSeen : d .sensorID ,
451456 }
452457}
453458
@@ -639,18 +644,36 @@ func (d *Datastore) writeBlockBody(ctx context.Context, body *eth.BlockBody, has
639644 }
640645}
641646
642- // writeTransactions will write the transactions to datastore and return the
643- // transaction hashes .
647+ // writeTransactions will write the transactions to datastore, skipping
648+ // transactions that already exist with an earlier or equal TimeFirstSeen .
644649func (d * Datastore ) writeTransactions (ctx context.Context , txs []* types.Transaction , tfs time.Time ) {
645650 keys := make ([]* datastore.Key , 0 , len (txs ))
646- transactions := make ([]* DatastoreTransaction , 0 , len (txs ))
647-
648651 for _ , tx := range txs {
649652 keys = append (keys , datastore .NameKey (TransactionsKind , tx .Hash ().Hex (), nil ))
653+ }
654+
655+ // Fetch existing transactions to check TimeFirstSeen
656+ existing := make ([]* DatastoreTransaction , len (keys ))
657+ _ = d .client .GetMulti (ctx , keys , existing ) // Ignore errors - missing keys return nil
658+
659+ transactions := make ([]* DatastoreTransaction , 0 , len (txs ))
660+ keysToWrite := make ([]* datastore.Key , 0 , len (txs ))
661+
662+ for i , tx := range txs {
663+ // Skip if existing record has an earlier or equal TimeFirstSeen
664+ if existing [i ] != nil && ! existing [i ].TimeFirstSeen .IsZero () && ! tfs .Before (existing [i ].TimeFirstSeen ) {
665+ continue
666+ }
667+
668+ keysToWrite = append (keysToWrite , keys [i ])
650669 transactions = append (transactions , d .newDatastoreTransaction (tx , tfs ))
651670 }
652671
653- if _ , err := d .client .PutMulti (ctx , keys , transactions ); err != nil {
672+ if len (keysToWrite ) == 0 {
673+ return
674+ }
675+
676+ if _ , err := d .client .PutMulti (ctx , keysToWrite , transactions ); err != nil {
654677 log .Error ().Err (err ).Msg ("Failed to write transactions" )
655678 }
656679}
@@ -659,6 +682,10 @@ func (d *Datastore) ShouldWriteFirstBlockEvent() bool {
659682 return d .shouldWriteFirstBlockEvent
660683}
661684
685+ func (d * Datastore ) ShouldWriteFirstTransactionEvent () bool {
686+ return d .shouldWriteFirstTransactionEvent
687+ }
688+
662689func (d * Datastore ) NodeList (ctx context.Context , limit int ) ([]string , error ) {
663690 query := datastore .NewQuery (BlockEventsKind ).Order ("-Time" )
664691 iter := d .client .Run (ctx , query )
0 commit comments