Skip to content

Commit e777671

Browse files
authored
Merge pull request #911 from onflow/mpeter/tx-submission-fetch-single-account-key
Update tx submission logic to only fetch a single account key from the COA
2 parents dd10cb6 + 253d859 commit e777671

File tree

8 files changed

+69
-65
lines changed

8 files changed

+69
-65
lines changed

bootstrap/bootstrap.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ func (b *Bootstrap) Run(
694694
// mark ready
695695
ready()
696696

697+
go b.trackOperatorBalance(ctx)
698+
697699
return nil
698700
}
699701

@@ -707,6 +709,27 @@ func (b *Bootstrap) Stop() {
707709
b.StopDB()
708710
}
709711

712+
func (b *Bootstrap) trackOperatorBalance(ctx context.Context) {
713+
ticker := time.NewTicker(1 * time.Minute)
714+
defer ticker.Stop()
715+
716+
for {
717+
select {
718+
case <-ctx.Done():
719+
return
720+
case <-ticker.C:
721+
accBalance, err := b.client.GetAccountBalanceAtLatestBlock(ctx, b.config.COAAddress)
722+
if err != nil {
723+
b.logger.Warn().Err(err).Msg(
724+
"failed to collect operator's balance metric",
725+
)
726+
continue
727+
}
728+
b.collector.OperatorBalance(accBalance)
729+
}
730+
}
731+
}
732+
710733
// Run will run complete bootstrap of the EVM gateway with all the engines.
711734
// Run is a blocking call, but it does signal readiness of the service
712735
// through a channel provided as an argument.

metrics/collector.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"time"
66

7-
"github.com/onflow/flow-go-sdk"
87
"github.com/prometheus/client_golang/prometheus"
98
"github.com/rs/zerolog"
109
)
@@ -113,7 +112,7 @@ type Collector interface {
113112
EVMTransactionIndexed(count int)
114113
EVMAccountInteraction(address string)
115114
MeasureRequestDuration(start time.Time, method string)
116-
OperatorBalance(account *flow.Account)
115+
OperatorBalance(balance uint64)
117116
AvailableSigningKeys(count int)
118117
GasEstimationIterations(count int)
119118
BlockIngestionTime(blockCreation time.Time)
@@ -208,8 +207,8 @@ func (c *DefaultCollector) EVMAccountInteraction(address string) {
208207
c.evmAccountCallCounters.With(prometheus.Labels{"address": address}).Inc()
209208
}
210209

211-
func (c *DefaultCollector) OperatorBalance(account *flow.Account) {
212-
c.operatorBalance.Set(float64(account.Balance))
210+
func (c *DefaultCollector) OperatorBalance(balance uint64) {
211+
c.operatorBalance.Set(float64(balance))
213212
}
214213

215214
func (c *DefaultCollector) MeasureRequestDuration(start time.Time, method string) {

metrics/nop.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package metrics
22

33
import (
44
"time"
5-
6-
"github.com/onflow/flow-go-sdk"
75
)
86

97
type nopCollector struct{}
@@ -19,7 +17,7 @@ func (c *nopCollector) EVMHeightIndexed(uint64) {}
1917
func (c *nopCollector) EVMTransactionIndexed(int) {}
2018
func (c *nopCollector) EVMAccountInteraction(string) {}
2119
func (c *nopCollector) MeasureRequestDuration(time.Time, string) {}
22-
func (c *nopCollector) OperatorBalance(*flow.Account) {}
20+
func (c *nopCollector) OperatorBalance(balance uint64) {}
2321
func (c *nopCollector) AvailableSigningKeys(count int) {}
2422
func (c *nopCollector) GasEstimationIterations(count int) {}
2523
func (c *nopCollector) BlockIngestionTime(blockCreation time.Time) {}

services/requester/batch_tx_pool.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
161161
case <-ctx.Done():
162162
return
163163
case <-ticker.C:
164-
latestBlock, account, err := t.fetchFlowLatestBlockAndCOA(ctx)
164+
latestBlock, err := t.client.GetLatestBlock(ctx, true)
165165
if err != nil {
166166
t.logger.Error().Err(err).Msg(
167-
"failed to get COA / latest Flow block on batch tx submission",
167+
"failed to get latest Flow block on batch tx submission",
168168
)
169169
continue
170170
}
@@ -181,7 +181,6 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
181181
err := t.batchSubmitTransactionsForSameAddress(
182182
ctx,
183183
latestBlock,
184-
account,
185184
pooledTxs,
186185
)
187186
if err != nil {
@@ -199,7 +198,6 @@ func (t *BatchTxPool) processPooledTransactions(ctx context.Context) {
199198
func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
200199
ctx context.Context,
201200
latestBlock *flow.Block,
202-
account *flow.Account,
203201
pooledTxs []pooledEvmTx,
204202
) error {
205203
// Sort the transactions based on their nonce, to make sure
@@ -220,8 +218,8 @@ func (t *BatchTxPool) batchSubmitTransactionsForSameAddress(
220218

221219
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
222220
flowTx, err := t.buildTransaction(
221+
ctx,
223222
latestBlock,
224-
account,
225223
script,
226224
cadence.NewArray(hexEncodedTxs),
227225
coinbaseAddress,
@@ -244,7 +242,7 @@ func (t *BatchTxPool) submitSingleTransaction(
244242
ctx context.Context,
245243
hexEncodedTx cadence.String,
246244
) error {
247-
latestBlock, account, err := t.fetchFlowLatestBlockAndCOA(ctx)
245+
latestBlock, err := t.client.GetLatestBlock(ctx, true)
248246
if err != nil {
249247
return err
250248
}
@@ -256,8 +254,8 @@ func (t *BatchTxPool) submitSingleTransaction(
256254

257255
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
258256
flowTx, err := t.buildTransaction(
257+
ctx,
259258
latestBlock,
260-
account,
261259
script,
262260
cadence.NewArray([]cadence.Value{hexEncodedTx}),
263261
coinbaseAddress,

services/requester/keystore/account_key.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,31 @@ func (k *AccountKey) SetLockMetadata(txID flowsdk.Identifier, referenceBlockHeig
5454
// SetProposerPayerAndSign sets the proposer, payer, and signs the transaction with the key.
5555
func (k *AccountKey) SetProposerPayerAndSign(
5656
tx *flowsdk.Transaction,
57-
account *flowsdk.Account,
57+
address flowsdk.Address,
58+
acckey *flowsdk.AccountKey,
5859
) error {
59-
if k.Address != account.Address {
60+
if acckey == nil {
61+
return fmt.Errorf("nil account key provided for address %s (index %d)", address, k.Index)
62+
}
63+
64+
if k.Address != address {
6065
return fmt.Errorf(
61-
"expected address: %v, got address: %v",
66+
"expected address: %s, got address: %s",
6267
k.Address,
63-
account.Address,
68+
address,
6469
)
6570
}
66-
if k.Index >= uint32(len(account.Keys)) {
71+
72+
if k.Index != acckey.Index {
6773
return fmt.Errorf(
68-
"key index: %d exceeds keys length: %d",
74+
"expected account key with index: %d, got key with index: %d",
6975
k.Index,
70-
len(account.Keys),
76+
acckey.Index,
7177
)
7278
}
73-
seqNumber := account.Keys[k.Index].SequenceNumber
7479

7580
return tx.
76-
SetProposalKey(k.Address, k.Index, seqNumber).
81+
SetProposalKey(k.Address, k.Index, acckey.SequenceNumber).
7782
SetPayer(k.Address).
7883
SignEnvelope(k.Address, k.Index, k.Signer)
7984
}

services/requester/keystore/key_store_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func TestKeySigning(t *testing.T) {
200200
}
201201
tx := sdk.NewTransaction()
202202

203-
err = key.SetProposerPayerAndSign(tx, account)
203+
err = key.SetProposerPayerAndSign(tx, address, accountKey)
204204
require.NoError(t, err)
205205

206206
assert.Equal(t, account.Address, tx.ProposalKey.Address)

services/requester/requester.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func NewEVM(
118118

119119
if !config.IndexOnly {
120120
address := config.COAAddress
121-
acc, err := client.GetAccount(context.Background(), address)
121+
accBalance, err := client.GetAccountBalanceAtLatestBlock(context.Background(), address)
122122
if err != nil {
123123
return nil, fmt.Errorf(
124124
"could not fetch the configured COA account: %s make sure it exists: %w",
@@ -127,13 +127,13 @@ func NewEVM(
127127
)
128128
}
129129
// initialize the operator balance metric since it is only updated when sending a tx
130-
collector.OperatorBalance(acc)
130+
collector.OperatorBalance(accBalance)
131131

132-
if acc.Balance < minFlowBalance {
132+
if accBalance < minFlowBalance {
133133
return nil, fmt.Errorf(
134134
"COA account must be funded with at least %d Flow, but has balance of: %d",
135135
minFlowBalance,
136-
acc.Balance,
136+
accBalance,
137137
)
138138
}
139139
}

services/requester/single_tx_pool.go

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ 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"
@@ -93,15 +92,15 @@ func (t *SingleTxPool) Add(
9392
return err
9493
}
9594

96-
latestBlock, account, err := t.fetchFlowLatestBlockAndCOA(ctx)
95+
latestBlock, err := t.client.GetLatestBlock(ctx, true)
9796
if err != nil {
9897
return err
9998
}
10099

101100
script := replaceAddresses(runTxScript, t.config.FlowNetworkID)
102101
flowTx, err := t.buildTransaction(
102+
ctx,
103103
latestBlock,
104-
account,
105104
script,
106105
cadence.NewArray([]cadence.Value{hexEncodedTx}),
107106
coinbaseAddress,
@@ -157,8 +156,8 @@ func (t *SingleTxPool) Add(
157156
// buildTransaction creates a Cadence transaction from the provided script,
158157
// with the given arguments and signs it with the configured COA account.
159158
func (t *SingleTxPool) buildTransaction(
159+
ctx context.Context,
160160
latestBlock *flow.Block,
161-
account *flow.Account,
162161
script []byte,
163162
args ...cadence.Value,
164163
) (*flow.Transaction, error) {
@@ -177,53 +176,35 @@ func (t *SingleTxPool) buildTransaction(
177176
}
178177
}
179178

180-
// building and signing transactions should be blocking,
181-
// so we don't have keys conflict
182-
t.mux.Lock()
183-
defer t.mux.Unlock()
179+
accKey, err := t.fetchSigningAccountKey()
180+
if err != nil {
181+
return nil, err
182+
}
184183

185-
accKey, err := t.keystore.Take()
184+
coaAddress := t.config.COAAddress
185+
accountKey, err := t.client.GetAccountKeyAtLatestBlock(ctx, coaAddress, accKey.Index)
186186
if err != nil {
187+
accKey.Done()
187188
return nil, err
188189
}
189190

190-
if err := accKey.SetProposerPayerAndSign(flowTx, account); err != nil {
191+
if err := accKey.SetProposerPayerAndSign(flowTx, coaAddress, accountKey); err != nil {
191192
accKey.Done()
192193
return nil, err
193194
}
194195

195196
// now that the transaction is prepared, store the transaction's metadata
196197
accKey.SetLockMetadata(flowTx.ID(), latestBlock.Height)
197198

198-
t.collector.OperatorBalance(account)
199-
200199
return flowTx, nil
201200
}
202201

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-
)
214-
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
226-
}
202+
func (t *SingleTxPool) fetchSigningAccountKey() (*keystore.AccountKey, error) {
203+
// getting an account key from the `KeyStore` for signing transactions,
204+
// should be lock-protected, so that we don't sign any two Flow
205+
// transactions with the same account key
206+
t.mux.Lock()
207+
defer t.mux.Unlock()
227208

228-
return latestBlock, account, nil
209+
return t.keystore.Take()
229210
}

0 commit comments

Comments
 (0)