55 "encoding/hex"
66 "fmt"
77 "sync"
8+ "sync/atomic"
89 "time"
910
1011 gethTypes "github.com/ethereum/go-ethereum/core/types"
@@ -20,6 +21,8 @@ import (
2021 "github.com/onflow/flow-evm-gateway/services/requester/keystore"
2122)
2223
24+ const referenceBlockUpdateFrequency = time .Second * 15
25+
2326// SingleTxPool is a simple implementation of the `TxPool` interface that submits
2427// transactions as soon as they arrive, without any delays or batching strategies.
2528type SingleTxPool struct {
@@ -31,23 +34,32 @@ type SingleTxPool struct {
3134 mux sync.Mutex
3235 keystore * keystore.KeyStore
3336 collector metrics.Collector
37+ // referenceBlockHeader is stored atomically to avoid races
38+ // between request path and ticker updates.
39+ referenceBlockHeader atomic.Value // stores *flow.BlockHeader
3440 // todo add methods to inspect transaction pool state
3541}
3642
3743var _ TxPool = & SingleTxPool {}
3844
3945func NewSingleTxPool (
46+ ctx context.Context ,
4047 client * CrossSporkClient ,
4148 transactionsPublisher * models.Publisher [* gethTypes.Transaction ],
4249 logger zerolog.Logger ,
4350 config config.Config ,
4451 collector metrics.Collector ,
4552 keystore * keystore.KeyStore ,
46- ) * SingleTxPool {
53+ ) (* SingleTxPool , error ) {
54+ referenceBlockHeader , err := client .GetLatestBlockHeader (ctx , false )
55+ if err != nil {
56+ return nil , err
57+ }
58+
4759 // initialize the available keys metric since it is only updated when sending a tx
4860 collector .AvailableSigningKeys (keystore .AvailableKeys ())
4961
50- return & SingleTxPool {
62+ singleTxPool := & SingleTxPool {
5163 logger : logger .With ().Str ("component" , "tx-pool" ).Logger (),
5264 client : client ,
5365 txPublisher : transactionsPublisher ,
@@ -56,6 +68,11 @@ func NewSingleTxPool(
5668 collector : collector ,
5769 keystore : keystore ,
5870 }
71+ singleTxPool .referenceBlockHeader .Store (referenceBlockHeader )
72+
73+ go singleTxPool .updateReferenceBlock (ctx )
74+
75+ return singleTxPool , nil
5976}
6077
6178// Add creates a Cadence transaction that wraps the given EVM transaction in
@@ -92,15 +109,10 @@ func (t *SingleTxPool) Add(
92109 return err
93110 }
94111
95- latestBlock , err := t .client .GetLatestBlock (ctx , true )
96- if err != nil {
97- return err
98- }
99-
100112 script := replaceAddresses (runTxScript , t .config .FlowNetworkID )
101113 flowTx , err := t .buildTransaction (
102114 ctx ,
103- latestBlock ,
115+ t . getReferenceBlock () ,
104116 script ,
105117 cadence .NewArray ([]cadence.Value {hexEncodedTx }),
106118 coinbaseAddress ,
@@ -157,7 +169,7 @@ func (t *SingleTxPool) Add(
157169// with the given arguments and signs it with the configured COA account.
158170func (t * SingleTxPool ) buildTransaction (
159171 ctx context.Context ,
160- latestBlock * flow.Block ,
172+ referenceBlockHeader * flow.BlockHeader ,
161173 script []byte ,
162174 args ... cadence.Value ,
163175) (* flow.Transaction , error ) {
@@ -167,7 +179,7 @@ func (t *SingleTxPool) buildTransaction(
167179
168180 flowTx := flow .NewTransaction ().
169181 SetScript (script ).
170- SetReferenceBlockID (latestBlock .ID ).
182+ SetReferenceBlockID (referenceBlockHeader .ID ).
171183 SetComputeLimit (flowGo .DefaultMaxTransactionGasLimit )
172184
173185 for _ , arg := range args {
@@ -194,7 +206,7 @@ func (t *SingleTxPool) buildTransaction(
194206 }
195207
196208 // now that the transaction is prepared, store the transaction's metadata
197- accKey .SetLockMetadata (flowTx .ID (), latestBlock .Height )
209+ accKey .SetLockMetadata (flowTx .ID (), referenceBlockHeader .Height )
198210
199211 return flowTx , nil
200212}
@@ -208,3 +220,31 @@ func (t *SingleTxPool) fetchSigningAccountKey() (*keystore.AccountKey, error) {
208220
209221 return t .keystore .Take ()
210222}
223+
224+ func (t * SingleTxPool ) getReferenceBlock () * flow.BlockHeader {
225+ if v := t .referenceBlockHeader .Load (); v != nil {
226+ return v .(* flow.BlockHeader )
227+ }
228+ return nil
229+ }
230+
231+ func (t * SingleTxPool ) updateReferenceBlock (ctx context.Context ) {
232+ ticker := time .NewTicker (referenceBlockUpdateFrequency )
233+ defer ticker .Stop ()
234+
235+ for {
236+ select {
237+ case <- ctx .Done ():
238+ return
239+ case <- ticker .C :
240+ blockHeader , err := t .client .GetLatestBlockHeader (ctx , false )
241+ if err != nil {
242+ t .logger .Error ().Err (err ).Msg (
243+ "failed to update the reference block" ,
244+ )
245+ continue
246+ }
247+ t .referenceBlockHeader .Store (blockHeader )
248+ }
249+ }
250+ }
0 commit comments