Skip to content

Commit

Permalink
Enable transaction validation with local state index
Browse files Browse the repository at this point in the history
  • Loading branch information
m-Peter committed Dec 5, 2024
1 parent 4b32ea8 commit 7597ff0
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 131 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ start-local:
--gas-price=0 \
--log-writer=console \
--profiler-enabled=true \
--profiler-port=6060
--profiler-port=6060 \
--tx-state-validation=local-index

# Use this after running `make build`, to test out the binary
.PHONY: start-local-bin
Expand All @@ -73,4 +74,5 @@ start-local-bin:
--gas-price=0 \
--log-writer=console \
--profiler-enabled=true \
--profiler-port=6060
--profiler-port=6060 \
--tx-state-validation=local-index
27 changes: 20 additions & 7 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"
"time"

"github.com/onflow/go-ethereum/core"
gethVM "github.com/onflow/go-ethereum/core/vm"
gethLog "github.com/onflow/go-ethereum/log"
"github.com/onflow/go-ethereum/rpc"
Expand Down Expand Up @@ -427,6 +428,17 @@ type responseHandler struct {
metrics metrics.Collector
}

var knownErrors = []error{
errs.ErrRateLimit,
errs.ErrInvalid,
errs.ErrFailedTransaction,
errs.ErrEndpointNotSupported,
gethVM.ErrExecutionReverted,
core.ErrNonceTooLow,
core.ErrNonceTooHigh,
core.ErrInsufficientFunds,
}

const errMethodNotFound = -32601
const errCodePanic = -32603

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

// don't error log known handled errors
if !errorIs(errMsg, errs.ErrRateLimit) &&
!errorIs(errMsg, errs.ErrInvalid) &&
!errorIs(errMsg, errs.ErrFailedTransaction) &&
!errorIs(errMsg, errs.ErrEndpointNotSupported) &&
!errorIs(errMsg, gethVM.ErrExecutionReverted) {
if !isKnownError(errMsg) {
// log the response error as a warning
l.Warn().Err(errors.New(errMsg)).Msg("API response")
}
Expand Down Expand Up @@ -505,6 +513,11 @@ func (w *responseHandler) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
}

func errorIs(msg string, err error) bool {
return strings.Contains(msg, err.Error())
func isKnownError(errMsg string) bool {
for _, err := range knownErrors {
if strings.Contains(errMsg, err.Error()) {
return true
}
}
return false
}
7 changes: 7 additions & 0 deletions api/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
errs "github.com/onflow/flow-evm-gateway/models/errors"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/core"
"github.com/onflow/go-ethereum/core/types"
"github.com/onflow/go-ethereum/rpc"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -124,6 +125,12 @@ func handleError[T any](err error, log zerolog.Logger, collector metrics.Collect
return zero, err
case errors.As(err, &revertedErr):
return zero, revertedErr
case errors.Is(err, core.ErrNonceTooLow):
return zero, err
case errors.Is(err, core.ErrNonceTooHigh):
return zero, err
case errors.Is(err, core.ErrInsufficientFunds):
return zero, err
default:
collector.ApiErrorOccurred()
log.Error().Err(err).Msg("api error")
Expand Down
1 change: 1 addition & 0 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
b.client,
b.publishers.Transaction,
b.logger,
b.config,
)

blocksProvider := replayer.NewBlocksProvider(
Expand Down
12 changes: 11 additions & 1 deletion cmd/run/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ func parseConfigFromFlags() error {
log.Warn().Msg("wallet API is enabled. Ensure this is not used in production environments.")
}

if txStateValidation == config.LocalIndexValidation {
cfg.TxStateValidation = config.LocalIndexValidation
} else if txStateValidation == config.TxSealValidation {
cfg.TxStateValidation = config.TxSealValidation
} else {
return fmt.Errorf("unknown tx state validation: %s", txStateValidation)
}

return nil
}

Expand All @@ -261,7 +269,8 @@ var (
cloudKMSProjectID,
cloudKMSLocationID,
cloudKMSKeyRingID,
walletKey string
walletKey,
txStateValidation string

streamTimeout int

Expand Down Expand Up @@ -303,4 +312,5 @@ func init() {
Cmd.Flags().BoolVar(&cfg.ProfilerEnabled, "profiler-enabled", false, "Run the profiler server to capture pprof data.")
Cmd.Flags().StringVar(&cfg.ProfilerHost, "profiler-host", "localhost", "Host for the Profiler server")
Cmd.Flags().IntVar(&cfg.ProfilerPort, "profiler-port", 6060, "Port for the Profiler server")
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'.")
}
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ const EmulatorInitCadenceHeight = uint64(0)
// We don't use 0 as it has a special meaning to represent latest block in the AN API context.
const LiveNetworkInitCadenceHeight = uint64(1)

type TxStateValidation string

const (
LocalIndexValidation = "local-index"
TxSealValidation = "tx-seal"
)

type Config struct {
// DatabaseDir is where the database should be stored.
DatabaseDir string
Expand Down Expand Up @@ -85,4 +92,7 @@ type Config struct {
ProfilerHost string
// ProfilerPort is the port for the profiler server
ProfilerPort int
// TxStateValidation sets the transaction validation mechanism. It can validate
// using the local state index, or wait for the outer Flow transaction to seal.
TxStateValidation string
}
62 changes: 35 additions & 27 deletions services/requester/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/rs/zerolog"
"github.com/sethvargo/go-retry"

"github.com/onflow/flow-evm-gateway/config"
"github.com/onflow/flow-evm-gateway/models"
errs "github.com/onflow/flow-evm-gateway/models/errors"
)
Expand All @@ -30,19 +31,22 @@ type TxPool struct {
client *CrossSporkClient
pool *sync.Map
txPublisher *models.Publisher[*gethTypes.Transaction]
config config.Config
// todo add methods to inspect transaction pool state
}

func NewTxPool(
client *CrossSporkClient,
transactionsPublisher *models.Publisher[*gethTypes.Transaction],
logger zerolog.Logger,
config config.Config,
) *TxPool {
return &TxPool{
logger: logger.With().Str("component", "tx-pool").Logger(),
client: client,
txPublisher: transactionsPublisher,
pool: &sync.Map{},
config: config,
}
}

Expand All @@ -61,37 +65,41 @@ func (t *TxPool) Send(
return err
}

// add to pool and delete after transaction is sealed or errored out
t.pool.Store(evmTx.Hash(), evmTx)
defer t.pool.Delete(evmTx.Hash())

backoff := retry.WithMaxDuration(time.Minute*1, retry.NewConstant(time.Second*1))
return retry.Do(ctx, backoff, func(ctx context.Context) error {
res, err := t.client.GetTransactionResult(ctx, flowTx.ID())
if err != nil {
return fmt.Errorf("failed to retrieve flow transaction result %s: %w", flowTx.ID(), err)
}
// retry until transaction is sealed
if res.Status < flow.TransactionStatusSealed {
return retry.RetryableError(fmt.Errorf("transaction %s not sealed", flowTx.ID()))
}

if res.Error != nil {
if err, ok := parseInvalidError(res.Error); ok {
return err
if t.config.TxStateValidation == config.TxSealValidation {
// add to pool and delete after transaction is sealed or errored out
t.pool.Store(evmTx.Hash(), evmTx)
defer t.pool.Delete(evmTx.Hash())

backoff := retry.WithMaxDuration(time.Minute*1, retry.NewConstant(time.Second*1))
return retry.Do(ctx, backoff, func(ctx context.Context) error {
res, err := t.client.GetTransactionResult(ctx, flowTx.ID())
if err != nil {
return fmt.Errorf("failed to retrieve flow transaction result %s: %w", flowTx.ID(), err)
}
// retry until transaction is sealed
if res.Status < flow.TransactionStatusSealed {
return retry.RetryableError(fmt.Errorf("transaction %s not sealed", flowTx.ID()))
}

t.logger.Error().Err(res.Error).
Str("flow-id", flowTx.ID().String()).
Str("evm-id", evmTx.Hash().Hex()).
Msg("flow transaction error")
if res.Error != nil {
if err, ok := parseInvalidError(res.Error); ok {
return err
}

t.logger.Error().Err(res.Error).
Str("flow-id", flowTx.ID().String()).
Str("evm-id", evmTx.Hash().Hex()).
Msg("flow transaction error")

// hide specific cause since it's an implementation issue
return fmt.Errorf("failed to submit flow evm transaction %s", evmTx.Hash())
}
// hide specific cause since it's an implementation issue
return fmt.Errorf("failed to submit flow evm transaction %s", evmTx.Hash())
}

return nil
})
}

return nil
})
return nil
}

// this will extract the evm specific error from the Flow transaction error message
Expand Down
Loading

0 comments on commit 7597ff0

Please sign in to comment.