diff --git a/api/api.go b/api/api.go index b417418a6..056c94c87 100644 --- a/api/api.go +++ b/api/api.go @@ -564,7 +564,6 @@ func handleError[T any](log zerolog.Logger, err error) (T, error) { NOT SUPPORTED SECTION ====================================================================================================================== */ -// todo check the design, maybe we don't need to even have the unsupported functions defined // GetUncleCountByBlockHash returns number of uncles in the block for the given block hash func (b *BlockChainAPI) GetUncleCountByBlockHash( diff --git a/api/server.go b/api/server.go index 73cc0d279..fadcf63dd 100644 --- a/api/server.go +++ b/api/server.go @@ -238,7 +238,6 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // If JSON-RPC over HTTP is enabled, try to serve the request - // todo wrap with CORS handler in main branch rpc := recoverHandler(h.logger, h.httpHandler) if rpc != nil { if checkPath(r, "") { diff --git a/config/config.go b/config/config.go index 6e468ff4f..9071eaf1c 100644 --- a/config/config.go +++ b/config/config.go @@ -3,14 +3,15 @@ package config import ( "flag" "fmt" - "github.com/goccy/go-json" - "github.com/onflow/flow-go/utils/io" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/goccy/go-json" "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/crypto" "github.com/onflow/flow-go/fvm/evm/emulator" + flowGo "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/io" ) type Config struct { @@ -21,12 +22,11 @@ type Config struct { // GRPCPort for the RPC API server RPCPort int // GRPCHost for the RPC API server - // todo maybe merge port into host as it's for AN RPCHost string // EVMNetworkID provides the EVM chain ID. EVMNetworkID *big.Int // FlowNetworkID is the Flow network ID that the EVM is hosted on (mainnet, testnet, emulator...) - FlowNetworkID string + FlowNetworkID flowGo.ChainID // Coinbase is EVM address that collects the EVM operator fees collected // when transactions are being submitted. Coinbase common.Address @@ -45,21 +45,21 @@ type Config struct { func FromFlags() (*Config, error) { cfg := &Config{} - var evmNetwork, coinbase, gas, coa, key, keysPath string + var evmNetwork, coinbase, gas, coa, key, keysPath, flowChainID string // parse from flags - flag.StringVar(&cfg.DatabaseDir, "database-dir", "./db", "path to the directory for the database") - flag.StringVar(&cfg.RPCHost, "rpc-host", "", "host for the RPC API server") - flag.IntVar(&cfg.RPCPort, "rpc-port", 8545, "port for the RPC API server") - flag.StringVar(&cfg.AccessNodeGRPCHost, "access-node-grpc-host", "localhost:3569", "host to the flow access node gRPC API") + flag.StringVar(&cfg.DatabaseDir, "database-dir", "./db", "Path to the directory for the database") + flag.StringVar(&cfg.RPCHost, "rpc-host", "", "Host for the RPC API server") + flag.IntVar(&cfg.RPCPort, "rpc-port", 8545, "Port for the RPC API server") + flag.StringVar(&cfg.AccessNodeGRPCHost, "access-node-grpc-host", "localhost:3569", "Host to the flow access node gRPC API") flag.StringVar(&evmNetwork, "evm-network-id", "testnet", "EVM network ID (testnet, mainnet)") - flag.StringVar(&cfg.FlowNetworkID, "flow-network-id", "emulator", "EVM network ID (emulator, previewnet)") - flag.StringVar(&coinbase, "coinbase", "", "coinbase address to use for fee collection") - flag.StringVar(&gas, "gas-price", "1", "static gas price used for EVM transactions") + flag.StringVar(&flowChainID, "flow-network-id", "flow-emulator", "Flow network ID (flow-emulator, flow-previewnet)") + flag.StringVar(&coinbase, "coinbase", "", "Coinbase address to use for fee collection") + flag.StringVar(&gas, "gas-price", "1", "Static gas price used for EVM transactions") flag.StringVar(&coa, "coa-address", "", "Flow address that holds COA account used for submitting transactions") - flag.StringVar(&key, "coa-key", "", "WARNING: do not use this flag in production! private key value for the COA address used for submitting transactions") + flag.StringVar(&key, "coa-key", "", "Private key value for the COA address used for submitting transactions") flag.StringVar(&keysPath, "coa-key-file", "", "File path that contains JSON array of COA keys used in key-rotation mechanism, this is exclusive with coa-key flag.") - flag.BoolVar(&cfg.CreateCOAResource, "coa-resource-create", false, "auto-create the COA resource in the Flow COA account provided if one doesn't exist") + flag.BoolVar(&cfg.CreateCOAResource, "coa-resource-create", false, "Auto-create the COA resource in the Flow COA account provided if one doesn't exist") flag.Parse() if coinbase == "" { @@ -112,6 +112,15 @@ func FromFlags() (*Config, error) { return nil, fmt.Errorf("EVM network ID not supported") } + switch flowChainID { + case "flow-previewnet": + cfg.FlowNetworkID = flowGo.Previewnet + case "flow-emulator": + cfg.FlowNetworkID = flowGo.Emulator + default: + return nil, fmt.Errorf("flow network ID not supported, only possible to specify 'flow-previewnet' or 'flow-emulator'") + } + if cfg.FlowNetworkID != "previewnet" && cfg.FlowNetworkID != "emulator" { return nil, fmt.Errorf("flow network ID is invalid, only allowed to set 'emulator' and 'previewnet'") } diff --git a/go.mod b/go.mod index 352be5e22..d15cce9b4 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/rs/zerolog v1.31.0 github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a + golang.org/x/sync v0.6.0 ) require ( @@ -171,7 +172,6 @@ require ( golang.org/x/crypto v0.18.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.20.0 // indirect - golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/integration/e2e_test.go b/integration/e2e_test.go index 2d54ca17b..0ccb0d58e 100644 --- a/integration/e2e_test.go +++ b/integration/e2e_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "github.com/onflow/flow-go-sdk/access/grpc" + "github.com/onflow/flow-go/fvm/evm/types" "math/big" "testing" "time" @@ -57,9 +58,8 @@ func TestIntegration_TransferValue(t *testing.T) { cancelIngestion() }() - fundAmount := int64(5) flowAmount, _ := cadence.NewUFix64("5.0") - fundWei := flowToWei(fundAmount) + fundWei := types.NewBalanceFromUFix64(flowAmount) // step 1, 2, and 3. - create COA and fund it res, err := fundEOA(emu, flowAmount, fundEOAAddress) @@ -67,7 +67,8 @@ func TestIntegration_TransferValue(t *testing.T) { require.NoError(t, res.Error) assert.Len(t, res.Events, 9) // 7 evm events + 2 cadence events - transferWei := flowToWei(1) + flowTransfer, _ := cadence.NewUFix64("1.0") + transferWei := types.NewBalanceFromUFix64(flowTransfer) fundEOAKey, err := crypto.HexToECDSA(fundEOARawKey) require.NoError(t, err) @@ -84,21 +85,21 @@ func TestIntegration_TransferValue(t *testing.T) { require.NoError(t, err) assert.Equal(t, uint64(2), blk.Height) - assert.Equal(t, fundWei.Cmp(blk.TotalSupply), 0) + assert.Zero(t, blk.TotalSupply.Cmp(fundWei)) require.Len(t, blk.TransactionHashes, 1) // block 3 comes from calling evm.call to transfer to eoa 1 blk, err = blocks.GetByHeight(3) require.NoError(t, err) assert.Equal(t, uint64(3), blk.Height) - assert.Equal(t, fundWei.Cmp(blk.TotalSupply), 0) + assert.Zero(t, blk.TotalSupply.Cmp(fundWei)) require.Len(t, blk.TransactionHashes, 1) // block 5 comes from calling evm.call to transfer from eoa 1 to eoa 2 blk, err = blocks.GetByHeight(5) require.NoError(t, err) assert.Equal(t, uint64(5), blk.Height) - assert.Equal(t, fundWei.Cmp(blk.TotalSupply), 0) + assert.Zero(t, blk.TotalSupply.Cmp(fundWei)) require.Len(t, blk.TransactionHashes, 1) // transaction 1 comes from evm.call to transfer from eoa 1 to eoa 2 @@ -108,7 +109,7 @@ func TestIntegration_TransferValue(t *testing.T) { tx, err := txs.Get(transferHash) require.NoError(t, err) assert.Equal(t, uint64(0), tx.Nonce()) - assert.Equal(t, transferWei, tx.Value()) + assert.Zero(t, tx.Value().Cmp(transferWei)) assert.Equal(t, &transferEOAAdress, tx.To()) assert.Equal(t, uint64(21_000), tx.Gas()) @@ -426,7 +427,7 @@ func TestE2E_API_DeployEvents(t *testing.T) { AccessNodeGRPCHost: "localhost:3569", // emulator RPCPort: 3001, RPCHost: "127.0.0.1", - FlowNetworkID: "emulator", + FlowNetworkID: "flow-emulator", EVMNetworkID: emulator.FlowEVMTestnetChainID, Coinbase: fundEOAAddress, COAAddress: gwAddress, @@ -479,7 +480,8 @@ func TestE2E_API_DeployEvents(t *testing.T) { // check balance balance, err := rpcTester.getBalance(fundEOAAddress) require.NoError(t, err) - assert.Equal(t, new(big.Int).Mul(big.NewInt(4), toWei), balance.ToInt()) + c, _ := cadence.NewUFix64("4.0") + assert.Zero(t, balance.ToInt().Cmp(types.NewBalanceFromUFix64(c))) // Step 4. - deploy contract nonce := uint64(0) @@ -783,7 +785,7 @@ func TestE2E_ConcurrentTransactionSubmission(t *testing.T) { RPCPort: 3001, RPCHost: "127.0.0.1", EVMNetworkID: emulator.FlowEVMTestnetChainID, - FlowNetworkID: "emulator", + FlowNetworkID: "flow-emulator", Coinbase: fundEOAAddress, COAAddress: *createdAddr, COAKeys: keys, diff --git a/integration/helpers.go b/integration/helpers.go index 65fe33001..3fd31e7c5 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -40,7 +40,6 @@ import ( const testPrivateKey = "61ceacbdce419e25ee8e7c2beceee170a05c9cab1e725a955b15ba94dcd747d2" var ( - toWei = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) logger = zerolog.New(os.Stdout) sc = systemcontracts.SystemContractsForChain(flow.Emulator) ) @@ -73,43 +72,13 @@ func startEmulator() (*server.EmulatorServer, error) { srv.Start() }() - time.Sleep(200 * time.Millisecond) // give it some time to start, dummy but ok for test - - e := srv.Emulator() - b, err := e.GetLatestBlock() - if err != nil { - return nil, err - } - // mine one block to make sure we start at height 1, which is expected on normal network - addr := flow.Address(e.ServiceKey().Address) - emptyTx := &flow.TransactionBody{ - ReferenceBlockID: b.ID(), - Script: []byte(`transaction{}`), - ProposalKey: flow.ProposalKey{ - Address: addr, - KeyIndex: 0, - SequenceNumber: 2, - }, - Payer: addr, - } - hasher, err := crypto.NewHasher(crypto.HashAlgorithm(crypto.SHA3_256)) - if err != nil { - return nil, err - } - if err := emptyTx.SignEnvelope(addr, 0, e.ServiceKey().PrivateKey, hasher); err != nil { - return nil, err - } - if err := e.SendTransaction(emptyTx); err != nil { - return nil, err - } + time.Sleep(1000 * time.Millisecond) // give it some time to start, dummy but ok for test return srv, nil } // startEventIngestionEngine will start up the sdkEvent engine with the grpc subscriber // listening for events. -// todo for now we return index storage as a way to check the data if it was correctly -// indexed this will be in future replaced with evm gateway API access func startEventIngestionEngine(ctx context.Context, dbDir string) ( storage.BlockIndexer, storage.ReceiptIndexer, @@ -351,11 +320,6 @@ func evmHexToCadenceBytes(address string) (cadence.Array, error) { return cadence.NewArray(res), nil } -// todo use types.NewBalanceFromUFix64(evmAmount) when flow-go updated -func flowToWei(flow int64) *big.Int { - return new(big.Int).Mul(big.NewInt(flow), toWei) -} - type contract struct { code string abiJSON string diff --git a/services/requester/requester.go b/services/requester/requester.go index 3cc887ecc..11f9ea677 100644 --- a/services/requester/requester.go +++ b/services/requester/requester.go @@ -5,22 +5,22 @@ import ( "context" _ "embed" "fmt" - "math/big" - "strings" - + "github.com/ethereum/go-ethereum/common" + gethCore "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + gethVM "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/rlp" - "github.com/onflow/flow-go-sdk" - "github.com/onflow/flow-go-sdk/crypto" - "github.com/rs/zerolog" - - "github.com/ethereum/go-ethereum/common" "github.com/onflow/cadence" + "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/access" - - gethCore "github.com/ethereum/go-ethereum/core" - gethVM "github.com/ethereum/go-ethereum/core/vm" + "github.com/onflow/flow-go-sdk/crypto" evmTypes "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/fvm/systemcontracts" + flowGo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" + "golang.org/x/sync/errgroup" + "math/big" + "strings" ) var ( @@ -83,14 +83,14 @@ type EVM struct { client access.Client address flow.Address signer crypto.Signer - network string // todo change the type to FVM type once the "previewnet" is added + chainID flowGo.ChainID } func NewEVM( client access.Client, address flow.Address, signer crypto.Signer, - network string, + chainID flowGo.ChainID, createCOA bool, logger zerolog.Logger, ) (*EVM, error) { @@ -120,7 +120,7 @@ func NewEVM( address: address, signer: signer, logger: logger, - network: network, + chainID: chainID, } // create COA on the account @@ -132,7 +132,7 @@ func NewEVM( evm.replaceAddresses(createCOAScript), cadence.UFix64(coaFundingBalance), ) - logger.Info().Err(err).Str("id", id.String()).Msg("COA resource auto-created") + logger.Warn().Err(err).Str("id", id.String()).Msg("COA resource auto-creation status") } return evm, nil @@ -170,11 +170,10 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, to = tx.To().String() } e.logger.Info(). - Str("evm ID", tx.Hash().Hex()). - Str("flow ID", flowID.Hex()). + Str("evm-id", tx.Hash().Hex()). + Str("flow-id", flowID.Hex()). Str("to", to). Str("value", tx.Value().String()). - Str("data", fmt.Sprintf("%x", tx.Data())). Msg("raw transaction sent") return tx.Hash(), nil @@ -183,14 +182,24 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, // signAndSend creates a flow transaction from the provided script with the arguments and signs it with the // configured COA account and then submits it to the network. func (e *EVM) signAndSend(ctx context.Context, script []byte, args ...cadence.Value) (flow.Identifier, error) { - latestBlock, err := e.client.GetLatestBlock(ctx, true) - if err != nil { - return flow.EmptyID, fmt.Errorf("failed to get latest flow block: %w", err) - } - - index, seqNum, err := e.getSignerNetworkInfo(ctx) - if err != nil { - return flow.EmptyID, fmt.Errorf("failed to get signer info: %w", err) + var ( + g = errgroup.Group{} + err1, err2 error + latestBlock *flow.Block + index int + seqNum uint64 + ) + // execute concurrently so we can speed up all the information we need for tx + g.Go(func() error { + latestBlock, err1 = e.client.GetLatestBlock(ctx, true) + return err1 + }) + g.Go(func() error { + index, seqNum, err2 = e.getSignerNetworkInfo(ctx) + return err2 + }) + if err := g.Wait(); err != nil { + return flow.EmptyID, err } flowTx := flow.NewTransaction(). @@ -201,43 +210,35 @@ func (e *EVM) signAndSend(ctx context.Context, script []byte, args ...cadence.Va AddAuthorizer(e.address) for _, arg := range args { - if err = flowTx.AddArgument(arg); err != nil { + if err := flowTx.AddArgument(arg); err != nil { return flow.EmptyID, fmt.Errorf("failed to add argument: %w", err) } } - if err = flowTx.SignEnvelope(e.address, index, e.signer); err != nil { + if err := flowTx.SignEnvelope(e.address, index, e.signer); err != nil { return flow.EmptyID, fmt.Errorf("failed to sign envelope: %w", err) } - err = e.client.SendTransaction(ctx, *flowTx) - if err != nil { + if err := e.client.SendTransaction(ctx, *flowTx); err != nil { return flow.EmptyID, fmt.Errorf("failed to send transaction: %w", err) } - // todo should we wait for the transaction result? - // we should handle a case where flow transaction is failed but we will get a result back, it would only be failed, - // but there is no evm transaction. So if we submit an evm tx and get back an ID and then we wait for receipt - // we would never get it, but this failure of sending flow transaction could somehow help with this case - // this is only used for debugging purposes - go func(tx *flow.Transaction) { - res, _ := e.client.GetTransactionResult(context.Background(), flowTx.ID()) + go func(id flow.Identifier) { + res, _ := e.client.GetTransactionResult(context.Background(), id) if res.Error != nil { e.logger.Error(). - Str("flow-id", flowTx.ID().String()). + Str("flow-id", id.String()). Err(res.Error). Msg("flow transaction failed to execute") return } e.logger.Debug(). - Str("flow-id", flowTx.ID().String()). - Uint64("cadence-height", res.BlockHeight). + Str("flow-id", id.String()). Str("events", fmt.Sprintf("%v", res.Events)). - Str("script", string(flowTx.Script)). Msg("flow transaction executed successfully") - }(flowTx) + }(flowTx.ID()) return flowTx.ID(), nil } @@ -353,7 +354,7 @@ func (e *EVM) EstimateGas(ctx context.Context, data []byte) (uint64, error) { func (e *EVM) getSignerNetworkInfo(ctx context.Context) (int, uint64, error) { account, err := e.client.GetAccount(ctx, e.address) if err != nil { - return 0, 0, err + return 0, 0, fmt.Errorf("failed to get signer info account: %w", err) } signerPub := e.signer.PublicKey() @@ -368,26 +369,16 @@ func (e *EVM) getSignerNetworkInfo(ctx context.Context) (int, uint64, error) { // replaceAddresses replace the addresses based on the network func (e *EVM) replaceAddresses(script []byte) []byte { - // todo use the FVM configured addresses once the previewnet is added, this should all be replaced once flow-go is updated - addresses := map[string]map[string]string{ - "previewnet": { - "EVM": "0xb6763b4399a888c8", - "FungibleToken": "0xa0225e7000ac82a9", - "FlowToken": "0x4445e7ad11568276", - }, - "emulator": { - "EVM": "0xf8d6e0586b0a20c7", - "FungibleToken": "0xee82856bf20e2aa6", - "FlowToken": "0x0ae53cb6e3f42a79", - }, - } + // make the list of all contracts we should replace address for + sc := systemcontracts.SystemContractsForChain(e.chainID) + contracts := []systemcontracts.SystemContract{sc.EVMContract, sc.FungibleToken, sc.FlowToken} s := string(script) // iterate over all the import name and address pairs and replace them in script - for imp, addr := range addresses[e.network] { + for _, contract := range contracts { s = strings.ReplaceAll(s, - fmt.Sprintf("import %s", imp), - fmt.Sprintf("import %s from %s", imp, addr), + fmt.Sprintf("import %s", contract.Name), + fmt.Sprintf("import %s from %s", contract.Name, contract.Address.HexWithPrefix()), ) }