Skip to content

Commit 1143ff4

Browse files
committed
Track the latest finalized block header to avoid fetching it on every tx submission
1 parent e777671 commit 1143ff4

File tree

4 files changed

+70
-34
lines changed

4 files changed

+70
-34
lines changed

bootstrap/bootstrap.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,9 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
253253

254254
// create transaction pool
255255
var txPool requester.TxPool
256+
var err error
256257
if b.config.TxBatchMode {
257-
txPool = requester.NewBatchTxPool(
258+
txPool, err = requester.NewBatchTxPool(
258259
ctx,
259260
b.client,
260261
b.publishers.Transaction,
@@ -264,7 +265,8 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
264265
b.keystore,
265266
)
266267
} else {
267-
txPool = requester.NewSingleTxPool(
268+
txPool, err = requester.NewSingleTxPool(
269+
ctx,
268270
b.client,
269271
b.publishers.Transaction,
270272
b.logger,
@@ -273,6 +275,9 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
273275
b.keystore,
274276
)
275277
}
278+
if err != nil {
279+
return fmt.Errorf("failed to create transaction pool: %w", err)
280+
}
276281

277282
evm, err := requester.NewEVM(
278283
b.storages.Registers,

services/requester/batch_tx_pool.go

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,22 @@ func NewBatchTxPool(
5757
config config.Config,
5858
collector metrics.Collector,
5959
keystore *keystore.KeyStore,
60-
) *BatchTxPool {
60+
) (*BatchTxPool, error) {
6161
// initialize the available keys metric since it is only updated when sending a tx
6262
collector.AvailableSigningKeys(keystore.AvailableKeys())
6363

64-
singleTxPool := NewSingleTxPool(
64+
singleTxPool, err := NewSingleTxPool(
65+
ctx,
6566
client,
6667
transactionsPublisher,
6768
logger,
6869
config,
6970
collector,
7071
keystore,
7172
)
73+
if err != nil {
74+
return nil, err
75+
}
7276

7377
eoaActivity := expirable.NewLRU[gethCommon.Address, time.Time](
7478
eoaActivityCacheSize,
@@ -84,7 +88,7 @@ func NewBatchTxPool(
8488

8589
go batchPool.processPooledTransactions(ctx)
8690

87-
return batchPool
91+
return batchPool, nil
8892
}
8993

9094
// Add adds the EVM transaction to the tx pool, grouped with the rest of the
@@ -161,14 +165,6 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
161165
case <-ctx.Done():
162166
return
163167
case <-ticker.C:
164-
latestBlock, err := t.client.GetLatestBlock(ctx, true)
165-
if err != nil {
166-
t.logger.Error().Err(err).Msg(
167-
"failed to get latest Flow block on batch tx submission",
168-
)
169-
continue
170-
}
171-
172168
// Take a copy here to allow `Add()` to continue accept
173169
// incoming EVM transactions, without blocking until the
174170
// batch transactions are submitted.
@@ -180,7 +176,7 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
180176
for address, pooledTxs := range txsGroupedByAddress {
181177
err := t.batchSubmitTransactionsForSameAddress(
182178
ctx,
183-
latestBlock,
179+
t.getReferenceBlock(),
184180
pooledTxs,
185181
)
186182
if err != nil {
@@ -197,7 +193,7 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
197193

198194
func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
199195
ctx context.Context,
200-
latestBlock *flow.Block,
196+
referenceBlockHeader *flow.BlockHeader,
201197
pooledTxs []pooledEvmTx,
202198
) error {
203199
// Sort the transactions based on their nonce, to make sure
@@ -219,7 +215,7 @@ func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
219215
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
220216
flowTx, err := t.buildTransaction(
221217
ctx,
222-
latestBlock,
218+
referenceBlockHeader,
223219
script,
224220
cadence.NewArray(hexEncodedTxs),
225221
coinbaseAddress,
@@ -242,11 +238,6 @@ func (t *BatchTxPool) submitSingleTransaction(
242238
ctx context.Context,
243239
hexEncodedTx cadence.String,
244240
) error {
245-
latestBlock, err := t.client.GetLatestBlock(ctx, true)
246-
if err != nil {
247-
return err
248-
}
249-
250241
coinbaseAddress, err := cadence.NewString(t.config.Coinbase.Hex())
251242
if err != nil {
252243
return err
@@ -255,7 +246,7 @@ func (t *BatchTxPool) submitSingleTransaction(
255246
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
256247
flowTx, err := t.buildTransaction(
257248
ctx,
258-
latestBlock,
249+
t.getReferenceBlock(),
259250
script,
260251
cadence.NewArray([]cadence.Value{hexEncodedTx}),
261252
coinbaseAddress,

services/requester/single_tx_pool.go

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
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.
2528
type 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

3743
var _ TxPool = &SingleTxPool{}
3844

3945
func 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.
158170
func (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+
}

tests/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func startEmulator(createTestAccounts bool) (*server.EmulatorServer, error) {
9090
GenesisTokenSupply: genesisToken,
9191
WithContracts: true,
9292
Host: "localhost",
93-
TransactionExpiry: 10,
93+
TransactionExpiry: flow.DefaultTransactionExpiry,
9494
TransactionMaxGasLimit: flow.DefaultMaxTransactionGasLimit,
9595
SetupEVMEnabled: true,
9696
SetupVMBridgeEnabled: true,

0 commit comments

Comments
 (0)