Skip to content

Commit dce819d

Browse files
committed
Track the latest finalized block header to avoid fetching it on every tx submission
1 parent 25b8222 commit dce819d

File tree

4 files changed

+75
-60
lines changed

4 files changed

+75
-60
lines changed

bootstrap/bootstrap.go

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

248248
// create transaction pool
249249
var txPool requester.TxPool
250+
var err error
250251
if b.config.TxBatchMode {
251-
txPool = requester.NewBatchTxPool(
252+
txPool, err = requester.NewBatchTxPool(
252253
ctx,
253254
b.client,
254255
b.publishers.Transaction,
@@ -258,7 +259,8 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
258259
b.keystore,
259260
)
260261
} else {
261-
txPool = requester.NewSingleTxPool(
262+
txPool, err = requester.NewSingleTxPool(
263+
ctx,
262264
b.client,
263265
b.publishers.Transaction,
264266
b.logger,
@@ -267,6 +269,9 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
267269
b.keystore,
268270
)
269271
}
272+
if err != nil {
273+
return fmt.Errorf("failed to create transaction pool: %w", err)
274+
}
270275

271276
evm, err := requester.NewEVM(
272277
b.storages.Registers,

services/requester/batch_tx_pool.go

Lines changed: 14 additions & 10 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,10 +165,10 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
161165
case <-ctx.Done():
162166
return
163167
case <-ticker.C:
164-
latestBlock, account, err := t.fetchFlowLatestBlockAndCOA(ctx)
168+
account, err := t.client.GetAccount(ctx, t.config.COAAddress)
165169
if err != nil {
166170
t.logger.Error().Err(err).Msg(
167-
"failed to get COA / latest Flow block on batch tx submission",
171+
"failed to get COA on batch tx submission",
168172
)
169173
continue
170174
}
@@ -180,7 +184,7 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
180184
for address, pooledTxs := range txsGroupedByAddress {
181185
err := t.batchSubmitTransactionsForSameAddress(
182186
ctx,
183-
latestBlock,
187+
t.latestBlockHeader,
184188
account,
185189
pooledTxs,
186190
)
@@ -198,7 +202,7 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
198202

199203
func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
200204
ctx context.Context,
201-
latestBlock *flow.Block,
205+
latestBlockHeader *flow.BlockHeader,
202206
account *flow.Account,
203207
pooledTxs []pooledEvmTx,
204208
) error {
@@ -220,7 +224,7 @@ func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
220224

221225
script := replaceAddresses(batchRunTxScript, t.config.FlowNetworkID)
222226
flowTx, err := t.buildTransaction(
223-
latestBlock,
227+
latestBlockHeader,
224228
account,
225229
script,
226230
cadence.NewArray(hexEncodedTxs),
@@ -244,7 +248,7 @@ func (t *BatchTxPool) submitSingleTransaction(
244248
ctx context.Context,
245249
hexEncodedTx cadence.String,
246250
) error {
247-
latestBlock, account, err := t.fetchFlowLatestBlockAndCOA(ctx)
251+
account, err := t.client.GetAccount(ctx, t.config.COAAddress)
248252
if err != nil {
249253
return err
250254
}
@@ -256,7 +260,7 @@ func (t *BatchTxPool) submitSingleTransaction(
256260

257261
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
258262
flowTx, err := t.buildTransaction(
259-
latestBlock,
263+
t.latestBlockHeader,
260264
account,
261265
script,
262266
hexEncodedTx,

services/requester/single_tx_pool.go

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,50 +13,63 @@ import (
1313
flowGo "github.com/onflow/flow-go/model/flow"
1414
"github.com/rs/zerolog"
1515
"github.com/sethvargo/go-retry"
16-
"golang.org/x/sync/errgroup"
1716

1817
"github.com/onflow/flow-evm-gateway/config"
1918
"github.com/onflow/flow-evm-gateway/metrics"
2019
"github.com/onflow/flow-evm-gateway/models"
2120
"github.com/onflow/flow-evm-gateway/services/requester/keystore"
2221
)
2322

23+
var blockHeaderUpdateFrequency = time.Second * 5
24+
2425
// SingleTxPool is a simple implementation of the `TxPool` interface that submits
2526
// transactions as soon as they arrive, without any delays or batching strategies.
2627
type SingleTxPool struct {
27-
logger zerolog.Logger
28-
client *CrossSporkClient
29-
pool *sync.Map
30-
txPublisher *models.Publisher[*gethTypes.Transaction]
31-
config config.Config
32-
mux sync.Mutex
33-
keystore *keystore.KeyStore
34-
collector metrics.Collector
28+
logger zerolog.Logger
29+
client *CrossSporkClient
30+
pool *sync.Map
31+
txPublisher *models.Publisher[*gethTypes.Transaction]
32+
config config.Config
33+
mux sync.Mutex
34+
keystore *keystore.KeyStore
35+
collector metrics.Collector
36+
latestBlockHeader *flow.BlockHeader
3537
// todo add methods to inspect transaction pool state
3638
}
3739

3840
var _ TxPool = &SingleTxPool{}
3941

4042
func NewSingleTxPool(
43+
ctx context.Context,
4144
client *CrossSporkClient,
4245
transactionsPublisher *models.Publisher[*gethTypes.Transaction],
4346
logger zerolog.Logger,
4447
config config.Config,
4548
collector metrics.Collector,
4649
keystore *keystore.KeyStore,
47-
) *SingleTxPool {
50+
) (*SingleTxPool, error) {
51+
latestBlockHeader, err := client.GetLatestBlockHeader(ctx, false)
52+
if err != nil {
53+
return nil, err
54+
}
55+
4856
// initialize the available keys metric since it is only updated when sending a tx
4957
collector.AvailableSigningKeys(keystore.AvailableKeys())
5058

51-
return &SingleTxPool{
52-
logger: logger.With().Str("component", "tx-pool").Logger(),
53-
client: client,
54-
txPublisher: transactionsPublisher,
55-
pool: &sync.Map{},
56-
config: config,
57-
collector: collector,
58-
keystore: keystore,
59+
singleTxPool := &SingleTxPool{
60+
logger: logger.With().Str("component", "tx-pool").Logger(),
61+
client: client,
62+
txPublisher: transactionsPublisher,
63+
pool: &sync.Map{},
64+
config: config,
65+
collector: collector,
66+
keystore: keystore,
67+
latestBlockHeader: latestBlockHeader,
5968
}
69+
70+
go singleTxPool.trackLatestBlockHeader(ctx)
71+
72+
return singleTxPool, nil
6073
}
6174

6275
// Add creates a Cadence transaction that wraps the given EVM transaction in
@@ -93,14 +106,14 @@ func (t *SingleTxPool) Add(
93106
return err
94107
}
95108

96-
latestBlock, account, err := t.fetchFlowLatestBlockAndCOA(ctx)
109+
account, err := t.client.GetAccount(ctx, t.config.COAAddress)
97110
if err != nil {
98111
return err
99112
}
100113

101114
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
102115
flowTx, err := t.buildTransaction(
103-
latestBlock,
116+
t.latestBlockHeader,
104117
account,
105118
script,
106119
hexEncodedTx,
@@ -157,7 +170,7 @@ func (t *SingleTxPool) Add(
157170
// buildTransaction creates a Cadence transaction from the provided script,
158171
// with the given arguments and signs it with the configured COA account.
159172
func (t *SingleTxPool) buildTransaction(
160-
latestBlock *flow.Block,
173+
latestBlockHeader *flow.BlockHeader,
161174
account *flow.Account,
162175
script []byte,
163176
args ...cadence.Value,
@@ -168,7 +181,7 @@ func (t *SingleTxPool) buildTransaction(
168181

169182
flowTx := flow.NewTransaction().
170183
SetScript(script).
171-
SetReferenceBlockID(latestBlock.ID).
184+
SetReferenceBlockID(latestBlockHeader.ID).
172185
SetComputeLimit(flowGo.DefaultMaxTransactionGasLimit)
173186

174187
for _, arg := range args {
@@ -193,37 +206,30 @@ func (t *SingleTxPool) buildTransaction(
193206
}
194207

195208
// now that the transaction is prepared, store the transaction's metadata
196-
accKey.SetLockMetadata(flowTx.ID(), latestBlock.Height)
209+
accKey.SetLockMetadata(flowTx.ID(), latestBlockHeader.Height)
197210

198211
t.collector.OperatorBalance(account)
199212

200213
return flowTx, nil
201214
}
202215

203-
func (t *SingleTxPool) fetchFlowLatestBlockAndCOA(ctx context.Context) (
204-
*flow.Block,
205-
*flow.Account,
206-
error,
207-
) {
208-
var (
209-
g = errgroup.Group{}
210-
err1, err2 error
211-
latestBlock *flow.Block
212-
account *flow.Account
213-
)
216+
func (t *SingleTxPool) trackLatestBlockHeader(ctx context.Context) {
217+
ticker := time.NewTicker(blockHeaderUpdateFrequency)
218+
defer ticker.Stop()
214219

215-
// execute concurrently so we can speed up all the information we need for tx
216-
g.Go(func() error {
217-
latestBlock, err1 = t.client.GetLatestBlock(ctx, true)
218-
return err1
219-
})
220-
g.Go(func() error {
221-
account, err2 = t.client.GetAccount(ctx, t.config.COAAddress)
222-
return err2
223-
})
224-
if err := g.Wait(); err != nil {
225-
return nil, nil, err
220+
for {
221+
select {
222+
case <-ctx.Done():
223+
return
224+
case <-ticker.C:
225+
blockHeader, err := t.client.GetLatestBlockHeader(ctx, false)
226+
if err != nil {
227+
t.logger.Error().Err(err).Msg(
228+
"failed to update latest Flow block header",
229+
)
230+
continue
231+
}
232+
t.latestBlockHeader = blockHeader
233+
}
226234
}
227-
228-
return latestBlock, account, nil
229235
}

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: false,

0 commit comments

Comments
 (0)