Skip to content

Commit 449e6de

Browse files
committed
Enable transaction validation with local state index
1 parent 3e67411 commit 449e6de

File tree

11 files changed

+298
-148
lines changed

11 files changed

+298
-148
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ EMULATOR_ARGS := --flow-network-id=flow-emulator \
2727
--log-writer=console \
2828
--tx-state-validation=local-index \
2929
--profiler-enabled=true \
30-
--profiler-port=6060
30+
--profiler-port=6060 \
31+
--tx-state-validation=local-index
3132

3233
# Set VERSION from command line, environment, or default to SHORT_COMMIT
3334
VERSION ?= ${SHORT_COMMIT}

api/server.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919
"time"
2020

21+
"github.com/onflow/go-ethereum/core"
2122
gethVM "github.com/onflow/go-ethereum/core/vm"
2223
gethLog "github.com/onflow/go-ethereum/log"
2324
"github.com/onflow/go-ethereum/rpc"
@@ -427,6 +428,17 @@ type responseHandler struct {
427428
metrics metrics.Collector
428429
}
429430

431+
var knownErrors = []error{
432+
errs.ErrRateLimit,
433+
errs.ErrInvalid,
434+
errs.ErrFailedTransaction,
435+
errs.ErrEndpointNotSupported,
436+
gethVM.ErrExecutionReverted,
437+
core.ErrNonceTooLow,
438+
core.ErrNonceTooHigh,
439+
core.ErrInsufficientFunds,
440+
}
441+
430442
const errMethodNotFound = -32601
431443
const errCodePanic = -32603
432444

@@ -471,11 +483,7 @@ func (w *responseHandler) Write(data []byte) (int, error) {
471483
}
472484

473485
// don't error log known handled errors
474-
if !errorIs(errMsg, errs.ErrRateLimit) &&
475-
!errorIs(errMsg, errs.ErrInvalid) &&
476-
!errorIs(errMsg, errs.ErrFailedTransaction) &&
477-
!errorIs(errMsg, errs.ErrEndpointNotSupported) &&
478-
!errorIs(errMsg, gethVM.ErrExecutionReverted) {
486+
if !isKnownError(errMsg) {
479487
// log the response error as a warning
480488
l.Warn().Err(errors.New(errMsg)).Msg("API response")
481489
}
@@ -505,6 +513,11 @@ func (w *responseHandler) WriteHeader(statusCode int) {
505513
w.ResponseWriter.WriteHeader(statusCode)
506514
}
507515

508-
func errorIs(msg string, err error) bool {
509-
return strings.Contains(msg, err.Error())
516+
func isKnownError(errMsg string) bool {
517+
for _, err := range knownErrors {
518+
if strings.Contains(errMsg, err.Error()) {
519+
return true
520+
}
521+
}
522+
return false
510523
}

api/utils.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
errs "github.com/onflow/flow-evm-gateway/models/errors"
1313
"github.com/onflow/flow-evm-gateway/storage"
1414
"github.com/onflow/go-ethereum/common"
15+
"github.com/onflow/go-ethereum/core"
1516
"github.com/onflow/go-ethereum/core/types"
1617
"github.com/onflow/go-ethereum/rpc"
1718
"github.com/rs/zerolog"
@@ -124,6 +125,12 @@ func handleError[T any](err error, log zerolog.Logger, collector metrics.Collect
124125
return zero, err
125126
case errors.As(err, &revertedErr):
126127
return zero, revertedErr
128+
case errors.Is(err, core.ErrNonceTooLow):
129+
return zero, err
130+
case errors.Is(err, core.ErrNonceTooHigh):
131+
return zero, err
132+
case errors.Is(err, core.ErrInsufficientFunds):
133+
return zero, err
127134
default:
128135
collector.ApiErrorOccurred()
129136
log.Error().Err(err).Msg("api error")

bootstrap/bootstrap.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
211211
b.client,
212212
b.publishers.Transaction,
213213
b.logger,
214+
b.config,
214215
)
215216

216217
blocksProvider := replayer.NewBlocksProvider(

cmd/run/cmd.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,14 @@ func parseConfigFromFlags() error {
241241
log.Warn().Msg("wallet API is enabled. Ensure this is not used in production environments.")
242242
}
243243

244+
if txStateValidation == config.LocalIndexValidation {
245+
cfg.TxStateValidation = config.LocalIndexValidation
246+
} else if txStateValidation == config.TxSealValidation {
247+
cfg.TxStateValidation = config.TxSealValidation
248+
} else {
249+
return fmt.Errorf("unknown tx state validation: %s", txStateValidation)
250+
}
251+
244252
return nil
245253
}
246254

@@ -261,7 +269,8 @@ var (
261269
cloudKMSProjectID,
262270
cloudKMSLocationID,
263271
cloudKMSKeyRingID,
264-
walletKey string
272+
walletKey,
273+
txStateValidation string
265274

266275
streamTimeout int
267276

@@ -303,4 +312,5 @@ func init() {
303312
Cmd.Flags().BoolVar(&cfg.ProfilerEnabled, "profiler-enabled", false, "Run the profiler server to capture pprof data.")
304313
Cmd.Flags().StringVar(&cfg.ProfilerHost, "profiler-host", "localhost", "Host for the Profiler server")
305314
Cmd.Flags().IntVar(&cfg.ProfilerPort, "profiler-port", 6060, "Port for the Profiler server")
315+
Cmd.Flags().StringVar(&txStateValidation, "tx-state-validation", "tx-seal", "Sets the transaction validation mechanism. It can validate using the local state index, or wait for the outer Flow transaction to seal. Available values ('local-index' / 'tx-seal'), defaults to 'tx-seal'.")
306316
}

config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ const EmulatorInitCadenceHeight = uint64(0)
2323
// We don't use 0 as it has a special meaning to represent latest block in the AN API context.
2424
const LiveNetworkInitCadenceHeight = uint64(1)
2525

26+
type TxStateValidation string
27+
28+
const (
29+
LocalIndexValidation = "local-index"
30+
TxSealValidation = "tx-seal"
31+
)
32+
2633
type Config struct {
2734
// DatabaseDir is where the database should be stored.
2835
DatabaseDir string
@@ -85,4 +92,7 @@ type Config struct {
8592
ProfilerHost string
8693
// ProfilerPort is the port for the profiler server
8794
ProfilerPort int
95+
// TxStateValidation sets the transaction validation mechanism. It can validate
96+
// using the local state index, or wait for the outer Flow transaction to seal.
97+
TxStateValidation string
8898
}

services/requester/pool.go

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/rs/zerolog"
1313
"github.com/sethvargo/go-retry"
1414

15+
"github.com/onflow/flow-evm-gateway/config"
1516
"github.com/onflow/flow-evm-gateway/models"
1617
errs "github.com/onflow/flow-evm-gateway/models/errors"
1718
)
@@ -30,19 +31,22 @@ type TxPool struct {
3031
client *CrossSporkClient
3132
pool *sync.Map
3233
txPublisher *models.Publisher[*gethTypes.Transaction]
34+
config config.Config
3335
// todo add methods to inspect transaction pool state
3436
}
3537

3638
func NewTxPool(
3739
client *CrossSporkClient,
3840
transactionsPublisher *models.Publisher[*gethTypes.Transaction],
3941
logger zerolog.Logger,
42+
config config.Config,
4043
) *TxPool {
4144
return &TxPool{
4245
logger: logger.With().Str("component", "tx-pool").Logger(),
4346
client: client,
4447
txPublisher: transactionsPublisher,
4548
pool: &sync.Map{},
49+
config: config,
4650
}
4751
}
4852

@@ -61,37 +65,41 @@ func (t *TxPool) Send(
6165
return err
6266
}
6367

64-
// add to pool and delete after transaction is sealed or errored out
65-
t.pool.Store(evmTx.Hash(), evmTx)
66-
defer t.pool.Delete(evmTx.Hash())
67-
68-
backoff := retry.WithMaxDuration(time.Minute*1, retry.NewConstant(time.Second*1))
69-
return retry.Do(ctx, backoff, func(ctx context.Context) error {
70-
res, err := t.client.GetTransactionResult(ctx, flowTx.ID())
71-
if err != nil {
72-
return fmt.Errorf("failed to retrieve flow transaction result %s: %w", flowTx.ID(), err)
73-
}
74-
// retry until transaction is sealed
75-
if res.Status < flow.TransactionStatusSealed {
76-
return retry.RetryableError(fmt.Errorf("transaction %s not sealed", flowTx.ID()))
77-
}
78-
79-
if res.Error != nil {
80-
if err, ok := parseInvalidError(res.Error); ok {
81-
return err
68+
if t.config.TxStateValidation == config.TxSealValidation {
69+
// add to pool and delete after transaction is sealed or errored out
70+
t.pool.Store(evmTx.Hash(), evmTx)
71+
defer t.pool.Delete(evmTx.Hash())
72+
73+
backoff := retry.WithMaxDuration(time.Minute*1, retry.NewConstant(time.Second*1))
74+
return retry.Do(ctx, backoff, func(ctx context.Context) error {
75+
res, err := t.client.GetTransactionResult(ctx, flowTx.ID())
76+
if err != nil {
77+
return fmt.Errorf("failed to retrieve flow transaction result %s: %w", flowTx.ID(), err)
78+
}
79+
// retry until transaction is sealed
80+
if res.Status < flow.TransactionStatusSealed {
81+
return retry.RetryableError(fmt.Errorf("transaction %s not sealed", flowTx.ID()))
8282
}
8383

84-
t.logger.Error().Err(res.Error).
85-
Str("flow-id", flowTx.ID().String()).
86-
Str("evm-id", evmTx.Hash().Hex()).
87-
Msg("flow transaction error")
84+
if res.Error != nil {
85+
if err, ok := parseInvalidError(res.Error); ok {
86+
return err
87+
}
88+
89+
t.logger.Error().Err(res.Error).
90+
Str("flow-id", flowTx.ID().String()).
91+
Str("evm-id", evmTx.Hash().Hex()).
92+
Msg("flow transaction error")
8893

89-
// hide specific cause since it's an implementation issue
90-
return fmt.Errorf("failed to submit flow evm transaction %s", evmTx.Hash())
91-
}
94+
// hide specific cause since it's an implementation issue
95+
return fmt.Errorf("failed to submit flow evm transaction %s", evmTx.Hash())
96+
}
97+
98+
return nil
99+
})
100+
}
92101

93-
return nil
94-
})
102+
return nil
95103
}
96104

97105
// this will extract the evm specific error from the Flow transaction error message

0 commit comments

Comments
 (0)