Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable validation of submitted transactions with local state index #693

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
51 changes: 27 additions & 24 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/onflow/flow-evm-gateway/metrics"
"github.com/onflow/flow-go-sdk/access"
"github.com/onflow/flow-go-sdk/access/grpc"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go/fvm/environment"
"github.com/onflow/flow-go/fvm/evm"
flowGo "github.com/onflow/flow-go/model/flow"
Expand Down Expand Up @@ -61,6 +60,7 @@ type Bootstrap struct {
events *ingestion.Engine
profiler *api.ProfileServer
db *pebbleDB.DB
keystore *requester.KeyStore
}

func New(config config.Config) (*Bootstrap, error) {
Expand Down Expand Up @@ -131,6 +131,7 @@ func (b *Bootstrap) StartEventIngestion(ctx context.Context) error {
b.logger,
b.client,
chainID,
b.keystore,
latestCadenceHeight,
)

Expand Down Expand Up @@ -184,33 +185,12 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {

b.server = api.NewServer(b.logger, b.collector, b.config)

// create the signer based on either a single coa key being provided and using a simple in-memory
// signer, or multiple keys being provided and using signer with key-rotation mechanism.
var signer crypto.Signer
var err error
switch {
case b.config.COAKey != nil:
signer, err = crypto.NewInMemorySigner(b.config.COAKey, crypto.SHA3_256)
case b.config.COAKeys != nil:
signer, err = requester.NewKeyRotationSigner(b.config.COAKeys, crypto.SHA3_256)
case len(b.config.COACloudKMSKeys) > 0:
signer, err = requester.NewKMSKeyRotationSigner(
ctx,
b.config.COACloudKMSKeys,
b.logger,
)
default:
return fmt.Errorf("must provide either single COA / keylist of COA keys / COA cloud KMS keys")
}
if err != nil {
return fmt.Errorf("failed to create a COA signer: %w", err)
}

// create transaction pool
txPool := requester.NewTxPool(
b.client,
b.publishers.Transaction,
b.logger,
b.config,
)

blocksProvider := replayer.NewBlocksProvider(
Expand All @@ -219,16 +199,39 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
nil,
)

accountKeys := make([]*requester.AccountKey, 0)
account, err := b.client.GetAccount(ctx, b.config.COAAddress)
if err != nil {
return fmt.Errorf(
"failed to get signer info account for address: %s, with: %w",
b.config.COAAddress,
err,
)
}
signer, err := createSigner(ctx, b.config, b.logger)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: After viewing this comment, I am not entirely sure if we should have one crypto.Signer object for each account key, or create one for each account key. Are there any thread-safe concerns here? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single signer cannot be used across go routines, so we need multiple. One for every account key.

Actually the crypto.PrivateKey is not thread safe either! So if multiple account keys have the same private key, you need to create multiple copies of crypto.PrivateKey.

Copy link
Collaborator Author

@m-Peter m-Peter Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we have 2 types of signers, one is the in-memory signer:

  1. crypto.NewInMemorySigner(config.COAKey, crypto.SHA3_256)
  2. The other is requester.NewKMSKeySigner, which doesn't deal at all with crypto.PrivateKey

Currently, only option 2. is supposed to be used in production. And I'm not sure if it has any thread-safety concerns, as it uses Cloud KMS for signing.

Option 1. is supposed to be used for local development / testing.

if err != nil {
return err
}
for _, key := range account.Keys {
m-Peter marked this conversation as resolved.
Show resolved Hide resolved
accountKeys = append(accountKeys, &requester.AccountKey{
AccountKey: *key,
Address: b.config.COAAddress,
Signer: signer,
})
}

b.keystore = requester.NewKeyStore(accountKeys)

evm, err := requester.NewEVM(
b.storages.Registers,
blocksProvider,
b.client,
b.config,
signer,
b.logger,
b.storages.Blocks,
txPool,
b.collector,
b.keystore,
)
if err != nil {
return fmt.Errorf("failed to create EVM requester: %w", err)
Expand Down
64 changes: 37 additions & 27 deletions bootstrap/create-multi-key-account.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ import (
"github.com/onflow/flow-go-sdk/access/grpc"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go-sdk/templates"
"github.com/onflow/flow-go/fvm/systemcontracts"
flowGo "github.com/onflow/flow-go/model/flow"
"golang.org/x/exp/rand"
)

var sc = systemcontracts.SystemContractsForChain(flowGo.Emulator)
var ftAddress = sc.FungibleToken.Address.HexWithPrefix()
var flowAddress = sc.FlowToken.Address.HexWithPrefix()

// RunCreateMultiKeyAccount command creates a new account with multiple keys, which are saved to keys.json for later
// use with running the gateway in a key-rotation mode (used with --coa-key-file flag).
func RunCreateMultiKeyAccount() {
Expand All @@ -27,8 +33,8 @@ func RunCreateMultiKeyAccount() {
flag.IntVar(&keyCount, "key-count", 20, "how many keys you want to create and assign to account")
flag.StringVar(&keyFlag, "signer-key", "", "signer key used to create the new account")
flag.StringVar(&addressFlag, "signer-address", "", "signer address used to create new account")
flag.StringVar(&ftFlag, "ft-address", "0xee82856bf20e2aa6", "address of fungible token contract")
flag.StringVar(&flowFlag, "flow-token-address", "0x0ae53cb6e3f42a79", "address of flow token contract")
flag.StringVar(&ftFlag, "ft-address", ftAddress, "address of fungible token contract")
flag.StringVar(&flowFlag, "flow-token-address", flowAddress, "address of flow token contract")
flag.StringVar(&hostFlag, "access-node-grpc-host", "localhost:3569", "host to the flow access node gRPC API")

flag.Parse()
Expand All @@ -48,16 +54,13 @@ func RunCreateMultiKeyAccount() {
panic(err)
}

address, keys, err := CreateMultiKeyAccount(client, keyCount, payer, ftFlag, flowFlag, key)
address, privateKey, err := CreateMultiKeyAccount(client, keyCount, payer, ftFlag, flowFlag, key)
if err != nil {
panic(err)
}

fmt.Println("Address: ", address.Hex())
fmt.Println("Keys:")
for _, pk := range keys {
fmt.Println(pk.String())
}
fmt.Println("Key: ", privateKey.String())
}

/*
Expand All @@ -71,35 +74,25 @@ func CreateMultiKeyAccount(
ftAddress string,
flowAddress string,
key crypto.PrivateKey,
) (*flow.Address, []crypto.PrivateKey, error) {
) (*flow.Address, crypto.PrivateKey, error) {
privateKey, err := randomPrivateKey()
if err != nil {
return nil, nil, err
}

privKeys := make([]*flow.AccountKey, keyCount)
pks := make([]crypto.PrivateKey, keyCount)
accountKeys := make([]*flow.AccountKey, keyCount)
for i := 0; i < keyCount; i++ {
seed := make([]byte, crypto.MinSeedLength)
_, err := rand.Read(seed)
if err != nil {
return nil, nil, err
}

pk, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
if err != nil {
return nil, nil, err
}

pks[i] = pk
privKeys[i] = &flow.AccountKey{
accountKeys[i] = &flow.AccountKey{
Index: uint32(i),
PublicKey: pk.PublicKey(),
PublicKey: privateKey.PublicKey(),
SigAlgo: crypto.ECDSA_P256,
HashAlgo: crypto.SHA3_256,
Weight: 1000,
}
}

var err error
keyList := make([]cadence.Value, keyCount)
for i, key := range privKeys {
for i, key := range accountKeys {
keyList[i], err = templates.AccountKeyToCadenceCryptoKey(key)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -197,7 +190,7 @@ func CreateMultiKeyAccount(
events := eventsFromTx(res)
createdAddrs := events.GetCreatedAddresses()

return createdAddrs[0], pks, nil
return createdAddrs[0], privateKey, nil
}

func CreateMultiCloudKMSKeysAccount(
Expand Down Expand Up @@ -410,3 +403,20 @@ transaction(publicKeys: [Crypto.KeyListEntry], contracts: {String: String}, fund
}
}
`)

// randomPrivateKey returns a randomly generated ECDSA P-256 private key.
func randomPrivateKey() (crypto.PrivateKey, error) {
seed := make([]byte, crypto.MinSeedLength)

_, err := rand.Read(seed)
if err != nil {
return nil, err
}

privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
if err != nil {
return nil, err
}

return privateKey, nil
}
40 changes: 40 additions & 0 deletions bootstrap/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package bootstrap

import (
"context"
"fmt"

"github.com/onflow/flow-evm-gateway/config"
"github.com/onflow/flow-evm-gateway/services/requester"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/rs/zerolog"
)

// createSigner creates the signer based on either a single coa key being
// provided and using a simple in-memory signer, or a Cloud KMS key being
// provided and using a Cloud KMS signer.
func createSigner(
ctx context.Context,
config config.Config,
logger zerolog.Logger,
) (crypto.Signer, error) {
var signer crypto.Signer
var err error
switch {
case config.COAKey != nil:
signer, err = crypto.NewInMemorySigner(config.COAKey, crypto.SHA3_256)
case config.COACloudKMSKey != nil:
signer, err = requester.NewKMSKeySigner(
ctx,
*config.COACloudKMSKey,
logger,
)
default:
return nil, fmt.Errorf("must provide either single COA / Cloud KMS key")
}
if err != nil {
return nil, fmt.Errorf("failed to create a COA signer: %w", err)
}

return signer, nil
}
Loading
Loading