From cad7545f8a6ecf8c4da970f5ec20298ea8ff3ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= <31522760+fedekunze@users.noreply.github.com> Date: Fri, 7 Jan 2022 14:15:44 +0100 Subject: [PATCH 01/12] deps: IBC v3 alpha2 (#892) --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index b2f7b55cf5..6b542d6760 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/cosmos/cosmos-sdk v0.44.5 github.com/cosmos/go-bip39 v1.0.0 - github.com/cosmos/ibc-go/v3 v3.0.0-alpha1 + github.com/cosmos/ibc-go/v3 v3.0.0-alpha2 github.com/davecgh/go-spew v1.1.1 github.com/ethereum/go-ethereum v1.10.11 github.com/gogo/protobuf v1.3.3 @@ -154,8 +154,6 @@ replace ( github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 // TODO: remove once v0.45 is released github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1 - // TODO: remove once v3 is released - github.com/cosmos/ibc-go/v3 => github.com/cosmos/ibc-go/v3 v3.0.0-alpha1.0.20220105123212-9d79e86da4a8 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 google.golang.org/grpc => google.golang.org/grpc v1.33.2 ) diff --git a/go.sum b/go.sum index 7ec11706be..75f5c4bc19 100644 --- a/go.sum +++ b/go.sum @@ -233,8 +233,8 @@ github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/iavl v0.17.3 h1:s2N819a2olOmiauVa0WAhoIJq9EhSXE9HDBAoR9k+8Y= github.com/cosmos/iavl v0.17.3/go.mod h1:prJoErZFABYZGDHka1R6Oay4z9PrNeFFiMKHDAMOi4w= -github.com/cosmos/ibc-go/v3 v3.0.0-alpha1.0.20220105123212-9d79e86da4a8 h1:q/LK9UziGTJu50Zc0makZ0Bmulr9THHLw2mYv8t/SqQ= -github.com/cosmos/ibc-go/v3 v3.0.0-alpha1.0.20220105123212-9d79e86da4a8/go.mod h1:bwKHn8kcQwrT7aPl6Lp9rQbm5jshEAu+LZkzPxfpKW0= +github.com/cosmos/ibc-go/v3 v3.0.0-alpha2 h1:06MJACAgW6ttYohMkey0Gg9gWHRtqKWFpc8LwfuKzPM= +github.com/cosmos/ibc-go/v3 v3.0.0-alpha2/go.mod h1:bwKHn8kcQwrT7aPl6Lp9rQbm5jshEAu+LZkzPxfpKW0= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ8VOAit/gyATedRgjvkVI+UCrLpyuU= github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76/go.mod h1:0mkLWIoZuQ7uBoospo5Q9zIpqq6rYCPJDSUdeCJvPM8= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= From aeb6aeb71507c2c38b111fa7eac19e76832518c0 Mon Sep 17 00:00:00 2001 From: yihuang Date: Thu, 13 Jan 2022 21:12:57 +0800 Subject: [PATCH 02/12] Problem: newPendingTransactions filter don't return ethereum tx hash (#900) --- CHANGELOG.md | 1 + rpc/ethereum/types/utils.go | 8 ++++++-- rpc/websockets.go | 17 ++++++++++++----- server/json_rpc.go | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1694ed3a01..ea8b26ea5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (rpc) [tharsis#865](https://github.com/tharsis/ethermint/pull/865) Fix RPC Filter parameters being ignored * (evm) [tharsis#871](https://github.com/tharsis/ethermint/pull/871) Set correct nonce in `EthCall` and `EstimateGas` grpc query. * (rpc) [tharsis#878](https://github.com/tharsis/ethermint/pull/878) Workaround to make GetBlock RPC api report correct block gas used. +* (rpc) [tharsis#900](https://github.com/tharsis/ethermint/pull/900) newPendingTransactions filter return ethereum tx hash. ## [v0.9.0] - 2021-12-01 diff --git a/rpc/ethereum/types/utils.go b/rpc/ethereum/types/utils.go index 290af5eba7..6e8990d76c 100644 --- a/rpc/ethereum/types/utils.go +++ b/rpc/ethereum/types/utils.go @@ -31,9 +31,13 @@ func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) (*evmtypes.MsgEther return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } - ethTx, ok := tx.(*evmtypes.MsgEthereumTx) + if len(tx.GetMsgs()) != 1 { + return nil, errors.New("not ethereum tx") + } + + ethTx, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) if !ok { - return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, evmtypes.MsgEthereumTx{}) + return nil, fmt.Errorf("invalid msg type %T, expected %T", tx, evmtypes.MsgEthereumTx{}) } return ethTx, nil } diff --git a/rpc/websockets.go b/rpc/websockets.go index 80679f8c81..1a6ef78b54 100644 --- a/rpc/websockets.go +++ b/rpc/websockets.go @@ -11,6 +11,7 @@ import ( "net/http" "sync" + "github.com/cosmos/cosmos-sdk/client" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/pkg/errors" @@ -73,7 +74,7 @@ type websocketsServer struct { logger log.Logger } -func NewWebsocketsServer(logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer { +func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer { logger = logger.With("api", "websocket-server") _, port, _ := net.SplitHostPort(cfg.JSONRPC.Address) @@ -82,7 +83,7 @@ func NewWebsocketsServer(logger log.Logger, tmWSClient *rpcclient.WSClient, cfg wsAddr: cfg.JSONRPC.WsAddress, certFile: cfg.TLS.CertificatePath, keyFile: cfg.TLS.KeyPath, - api: newPubSubAPI(logger, tmWSClient), + api: newPubSubAPI(clientCtx, logger, tmWSClient), logger: logger, } } @@ -293,16 +294,18 @@ type pubSubAPI struct { filtersMu *sync.RWMutex filters map[rpc.ID]*wsSubscription logger log.Logger + clientCtx client.Context } // newPubSubAPI creates an instance of the ethereum PubSub API. -func newPubSubAPI(logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI { +func newPubSubAPI(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI { logger = logger.With("module", "websocket-client") return &pubSubAPI{ events: rpcfilters.NewEventSystem(logger, tmWSClient), filtersMu: new(sync.RWMutex), filters: make(map[rpc.ID]*wsSubscription), logger: logger, + clientCtx: clientCtx, } } @@ -680,7 +683,11 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro select { case ev := <-txsCh: data, _ := ev.Data.(tmtypes.EventDataTx) - txHash := common.BytesToHash(tmtypes.Tx(data.Tx).Hash()) + ethTx, err := types.RawTxToEthTx(api.clientCtx, data.Tx) + if err != nil { + // not ethereum tx + panic("debug") + } api.filtersMu.RLock() for subID, wsSub := range api.filters { @@ -695,7 +702,7 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro Method: "eth_subscription", Params: &SubscriptionResult{ Subscription: subID, - Result: txHash, + Result: ethTx.Hash, }, } diff --git a/server/json_rpc.go b/server/json_rpc.go index 204427fce7..315cc850dc 100644 --- a/server/json_rpc.go +++ b/server/json_rpc.go @@ -75,7 +75,7 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn // allocate separate WS connection to Tendermint tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) - wsSrv := rpc.NewWebsocketsServer(ctx.Logger, tmWsClient, config) + wsSrv := rpc.NewWebsocketsServer(clientCtx, ctx.Logger, tmWsClient, config) wsSrv.Start() return httpSrv, httpSrvDone, nil } From 7d8664043e700628026f0ef08cc29c86ea780e73 Mon Sep 17 00:00:00 2001 From: yihuang Date: Fri, 14 Jan 2022 17:37:33 +0800 Subject: [PATCH 03/12] impr: support batch eth txs (#901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * support batch eth tx Closes: 896 Allow multiple MsgEthereumTx in single tx * fix transaction receipt api * fix tx receipt api and accumulate tx gas used * fix lint * fix test * fix rpc test * cleanup * fix cumulativeGasUsed and gasUsed * fix lint * Update app/ante/eth.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update app/ante/eth.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update rpc/ethereum/backend/utils.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * pr suggestions * typo * fix lint Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- CHANGELOG.md | 1 + app/ante/ante_test.go | 6 +- app/ante/eth.go | 249 +++++++++------------ app/ante/eth_test.go | 13 +- app/ante/handler_options.go | 3 +- app/ante/interfaces.go | 1 + rpc/ethereum/backend/backend.go | 49 ++-- rpc/ethereum/backend/utils.go | 76 +++++-- rpc/ethereum/namespaces/eth/api.go | 118 +++++++--- rpc/ethereum/namespaces/eth/filters/api.go | 1 - rpc/ethereum/types/utils.go | 100 +++++++-- rpc/websockets.go | 68 +++--- x/evm/client/rest/rest.go | 16 +- x/evm/keeper/keeper.go | 34 +++ x/evm/keeper/msg_server.go | 4 +- x/evm/keeper/state_transition.go | 9 +- x/evm/spec/02_state.md | 3 +- x/evm/spec/07_events.md | 1 + x/evm/types/events.go | 1 + x/evm/types/key.go | 2 + x/evm/types/utils.go | 19 +- x/evm/types/utils_test.go | 6 +- 22 files changed, 463 insertions(+), 317 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea8b26ea5f..86fdfdd503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (evm) [tharsis#827](https://github.com/tharsis/ethermint/issues/827) Speed up creation of event logs by using the slice insertion idiom with indices. * (ante) [tharsis#819](https://github.com/tharsis/ethermint/pull/819) remove redundant ante handlers * (app) [tharsis#873](https://github.com/tharsis/ethermint/pull/873) Validate code hash in GenesisAccount +* (evm) [tharsis#901](https://github.com/tharsis/ethermint/pull/901) Support multiple MsgEthereumTx in single tx. ### Bug Fixes diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index 5ca5309309..00b6a02ff6 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -146,7 +146,7 @@ func (suite AnteTestSuite) TestAnteHandler() { func() sdk.Tx { signedTx := evmtypes.NewTx( suite.app.EvmKeeper.ChainID(), - 3, + 6, &to, big.NewInt(10), 100000, @@ -167,7 +167,7 @@ func (suite AnteTestSuite) TestAnteHandler() { func() sdk.Tx { signedTx := evmtypes.NewTx( suite.app.EvmKeeper.ChainID(), - 4, + 7, &to, big.NewInt(10), 100000, @@ -186,7 +186,7 @@ func (suite AnteTestSuite) TestAnteHandler() { { "fail - CheckTx (cosmos tx is not valid)", func() sdk.Tx { - signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 4, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) + signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 8, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) signedTx.From = addr.Hex() txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) diff --git a/app/ante/eth.go b/app/ante/eth.go index 09d0bd67f7..9f74039c7d 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -35,10 +35,6 @@ func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator { // Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user // won't see the error message. func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - if tx == nil || len(tx.GetMsgs()) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") - } - chainID := esvd.evmKeeper.ChainID() params := esvd.evmKeeper.GetParams(ctx) @@ -47,26 +43,27 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s blockNum := big.NewInt(ctx.BlockHeight()) signer := ethtypes.MakeSigner(ethCfg, blockNum) - msg := tx.GetMsgs()[0] - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } + for _, msg := range tx.GetMsgs() { + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } - sender, err := signer.Sender(msgEthTx.AsTransaction()) - if err != nil { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrorInvalidSigner, - "couldn't retrieve sender address ('%s') from the ethereum transaction: %s", - msgEthTx.From, - err.Error(), - ) - } + sender, err := signer.Sender(msgEthTx.AsTransaction()) + if err != nil { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrorInvalidSigner, + "couldn't retrieve sender address ('%s') from the ethereum transaction: %s", + msgEthTx.From, + err.Error(), + ) + } - // set up the sender to the transaction field if not already - msgEthTx.From = sender.Hex() + // set up the sender to the transaction field if not already + msgEthTx.From = sender.Hex() + } - return next(ctx, msgEthTx, simulate) + return next(ctx, tx, simulate) } // EthAccountVerificationDecorator validates an account balance checks @@ -99,7 +96,7 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } txData, err := evmtypes.UnpackTxData(msgEthTx.Data) @@ -134,58 +131,6 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx return next(ctx, tx, simulate) } -// EthNonceVerificationDecorator checks that the account nonce from the transaction matches -// the sender account sequence. -type EthNonceVerificationDecorator struct { - ak evmtypes.AccountKeeper -} - -// NewEthNonceVerificationDecorator creates a new EthNonceVerificationDecorator -func NewEthNonceVerificationDecorator(ak evmtypes.AccountKeeper) EthNonceVerificationDecorator { - return EthNonceVerificationDecorator{ - ak: ak, - } -} - -// AnteHandle validates that the transaction nonces are valid and equivalent to the sender account’s -// current nonce. -func (nvd EthNonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // no need to check the nonce on ReCheckTx - if ctx.IsReCheckTx() { - return next(ctx, tx, simulate) - } - - for _, msg := range tx.GetMsgs() { - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } - - // sender address should be in the tx cache from the previous AnteHandle call - seq, err := nvd.ak.GetSequence(ctx, msgEthTx.GetFrom()) - if err != nil { - return ctx, sdkerrors.Wrapf(err, "sequence not found for address %s", msgEthTx.From) - } - - txData, err := evmtypes.UnpackTxData(msgEthTx.Data) - if err != nil { - return ctx, sdkerrors.Wrap(err, "failed to unpack tx data") - } - - // if multiple transactions are submitted in succession with increasing nonces, - // all will be rejected except the first, since the first needs to be included in a block - // before the sequence increments - if txData.GetNonce() != seq { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrInvalidSequence, - "invalid nonce; got %d, expected %d", txData.GetNonce(), seq, - ) - } - } - - return next(ctx, tx, simulate) -} - // EthGasConsumeDecorator validates enough intrinsic gas for the transaction and // gas consumption. type EthGasConsumeDecorator struct { @@ -231,7 +176,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula for _, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } txData, err := evmtypes.UnpackTxData(msgEthTx.Data) @@ -298,7 +243,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate for _, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } baseFee := ctd.evmKeeper.BaseFee(ctx, ethCfg) @@ -369,23 +314,40 @@ func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrem // this AnteHandler decorator. func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { for _, msg := range tx.GetMsgs() { - // increment sequence of all signers - for _, addr := range msg.GetSigners() { - acc := issd.ak.GetAccount(ctx, addr) + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } - if acc == nil { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrUnknownAddress, - "account %s (%s) is nil", common.BytesToAddress(addr.Bytes()), addr, - ) - } + txData, err := evmtypes.UnpackTxData(msgEthTx.Data) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to unpack tx data") + } - if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { - return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) - } + // increase sequence of sender + acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom()) + if acc == nil { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrUnknownAddress, + "account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()), + ) + } + nonce := acc.GetSequence() + + // we merged the nonce verification to nonce increment, so when tx includes multiple messages + // with same sender, they'll be accepted. + if txData.GetNonce() != nonce { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrInvalidSequence, + "invalid nonce; got %d, expected %d", txData.GetNonce(), nonce, + ) + } - issd.ak.SetAccount(ctx, acc) + if err := acc.SetSequence(nonce + 1); err != nil { + return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) } + + issd.ak.SetAccount(ctx, acc) } return next(ctx, tx, simulate) @@ -430,30 +392,31 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") } - if len(protoTx.GetMsgs()) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") - } - msg := protoTx.GetMsgs()[0] - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } - ethGasLimit := msgEthTx.GetGas() + txFee := sdk.Coins{} + txGasLimit := uint64(0) - txData, err := evmtypes.UnpackTxData(msgEthTx.Data) - if err != nil { - return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data") - } + for _, msg := range protoTx.GetMsgs() { + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + txGasLimit += msgEthTx.GetGas() - params := vbd.evmKeeper.GetParams(ctx) - chainID := vbd.evmKeeper.ChainID() - ethCfg := params.ChainConfig.EthereumConfig(chainID) - baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg) - if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType { - return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") - } + txData, err := evmtypes.UnpackTxData(msgEthTx.Data) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data") + } - ethFeeAmount := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))} + params := vbd.evmKeeper.GetParams(ctx) + chainID := vbd.evmKeeper.ChainID() + ethCfg := params.ChainConfig.EthereumConfig(chainID) + baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg) + if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType { + return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") + } + + txFee = txFee.Add(sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))) + } authInfo := protoTx.AuthInfo if len(authInfo.SignerInfos) > 0 { @@ -464,12 +427,12 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") } - if !authInfo.Fee.Amount.IsEqual(ethFeeAmount) { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee Amount") + if !authInfo.Fee.Amount.IsEqual(txFee) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee) } - if authInfo.Fee.GasLimit != ethGasLimit { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee GasLimit") + if authInfo.Fee.GasLimit != txGasLimit { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit) } sigs := protoTx.Signatures @@ -483,10 +446,14 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu // EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption // by setting the gas meter to infinite -type EthSetupContextDecorator struct{} +type EthSetupContextDecorator struct { + evmKeeper EVMKeeper +} -func NewEthSetUpContextDecorator() EthSetupContextDecorator { - return EthSetupContextDecorator{} +func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator { + return EthSetupContextDecorator{ + evmKeeper: evmKeeper, + } } func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { @@ -497,6 +464,9 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul } newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + // Reset transient gas used to prepare the execution of current cosmos tx. + // Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs. + esc.evmKeeper.ResetTransientGasUsed(ctx) return next(newCtx, tx, simulate) } @@ -523,31 +493,30 @@ func NewEthMempoolFeeDecorator(ek EVMKeeper, fmk evmtypes.FeeMarketKeeper) EthMe // is only ran on check tx. func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { if ctx.IsCheckTx() && !simulate { - if len(tx.GetMsgs()) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") - } - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) - } + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } - var feeAmt *big.Int - - params := mfd.evmKeeper.GetParams(ctx) - chainID := mfd.evmKeeper.ChainID() - ethCfg := params.ChainConfig.EthereumConfig(chainID) - evmDenom := params.EvmDenom - baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg) - if baseFee != nil { - feeAmt = msg.GetEffectiveFee(baseFee) - } else { - feeAmt = msg.GetFee() - } + var feeAmt *big.Int + + params := mfd.evmKeeper.GetParams(ctx) + chainID := mfd.evmKeeper.ChainID() + ethCfg := params.ChainConfig.EthereumConfig(chainID) + evmDenom := params.EvmDenom + baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg) + if baseFee != nil { + feeAmt = ethMsg.GetEffectiveFee(baseFee) + } else { + feeAmt = ethMsg.GetFee() + } - glDec := sdk.NewDec(int64(msg.GetGas())) - requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) - if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) + glDec := sdk.NewDec(int64(ethMsg.GetGas())) + requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) + if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) + } } } diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index f8f75fbc4b..e181e1fc96 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -32,7 +32,7 @@ func (suite AnteTestSuite) TestEthSigVerificationDecorator() { reCheckTx bool expPass bool }{ - {"ReCheckTx", nil, true, false}, + {"ReCheckTx", &invalidTx{}, true, false}, {"invalid transaction type", &invalidTx{}, false, false}, { "invalid sender", @@ -145,7 +145,8 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() { } func (suite AnteTestSuite) TestEthNonceVerificationDecorator() { - dec := ante.NewEthNonceVerificationDecorator(suite.app.AccountKeeper) + suite.SetupTest() + dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper) addr := tests.GenerateAddress() @@ -159,7 +160,7 @@ func (suite AnteTestSuite) TestEthNonceVerificationDecorator() { reCheckTx bool expPass bool }{ - {"ReCheckTx", nil, func() {}, true, true}, + {"ReCheckTx", &invalidTx{}, func() {}, true, false}, {"invalid transaction type", &invalidTx{}, func() {}, false, false}, {"sender account not found", tx, func() {}, false, false}, { @@ -406,13 +407,13 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { "invalid transaction type", &invalidTx{}, func() {}, - false, true, + false, false, }, { "no signers", evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), func() {}, - false, true, + false, false, }, { "account not set to store", @@ -467,7 +468,7 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { } func (suite AnteTestSuite) TestEthSetupContextDecorator() { - dec := ante.NewEthSetUpContextDecorator() + dec := ante.NewEthSetUpContextDecorator(suite.app.EvmKeeper) tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) testCases := []struct { diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 83d35a10d9..b960df3947 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -48,12 +48,11 @@ func (options HandlerOptions) Validate() error { func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { return sdk.ChainAnteDecorators( - NewEthSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first NewEthMempoolFeeDecorator(options.EvmKeeper, options.FeeMarketKeeper), // Check eth effective gas price against minimal-gas-prices NewEthValidateBasicDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper), - NewEthNonceVerificationDecorator(options.AccountKeeper), NewEthGasConsumeDecorator(options.EvmKeeper), NewCanTransferDecorator(options.EvmKeeper, options.FeeMarketKeeper), NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator. diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index 520965439c..cfe435ef8a 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -25,6 +25,7 @@ type EVMKeeper interface { ) (sdk.Coins, error) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int GetBalance(ctx sdk.Context, addr common.Address) *big.Int + ResetTransientGasUsed(ctx sdk.Context) } type protoTxProvider interface { diff --git a/rpc/ethereum/backend/backend.go b/rpc/ethereum/backend/backend.go index 16063c770a..0cb68c0560 100644 --- a/rpc/ethereum/backend/backend.go +++ b/rpc/ethereum/backend/backend.go @@ -66,7 +66,6 @@ type Backend interface { HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) PendingTransactions() ([]*sdk.Tx, error) - GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) GetCoinbase() (sdk.AccAddress, error) @@ -536,18 +535,6 @@ func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, erro return ethHeader, nil } -// GetTransactionLogs returns the logs given a transaction hash. -// It returns an error if there's an encoding error. -// If no logs are found for the tx hash, the error is nil. -func (e *EVMBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { - tx, err := e.GetTxByEthHash(txHash) - if err != nil { - return nil, err - } - - return TxLogsFromEvents(tx.TxResult.Events) -} - // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (e *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) { @@ -578,12 +565,12 @@ func (e *EVMBackend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) { blockLogs := [][]*ethtypes.Log{} for _, txResult := range blockRes.TxsResults { - logs, err := TxLogsFromEvents(txResult.Events) + logs, err := AllTxLogsFromEvents(txResult.Events) if err != nil { return nil, err } - blockLogs = append(blockLogs, logs) + blockLogs = append(blockLogs, logs...) } return blockLogs, nil @@ -667,7 +654,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac } for _, tx := range txs { - msg, err := evmtypes.UnwrapEthereumMsg(tx) + msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash) if err != nil { // not ethereum tx continue @@ -696,16 +683,18 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac return nil, errors.New("invalid ethereum tx") } + msgIndex, attrs := types.FindTxAttributes(res.TxResult.Events, hexTx) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + } + tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx) if err != nil { return nil, err } - if len(tx.GetMsgs()) != 1 { - return nil, errors.New("invalid ethereum tx") - } - - msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + // the `msgIndex` is inferred from tx events, should be within the bound. + msg, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx) if !ok { return nil, errors.New("invalid ethereum tx") } @@ -718,7 +707,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac // Try to find txIndex from events found := false - txIndex, err := types.TxIndexFromEvents(res.TxResult.Events) + txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex) if err == nil { found = true } else { @@ -1033,7 +1022,6 @@ func (e *EVMBackend) BaseFee(height int64) (*big.Int, error) { // GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block. // It also ensures consistency over the correct txs indexes across RPC endpoints func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx { - // nolint: prealloc var result []*evmtypes.MsgEthereumTx txResults := blockRes.TxsResults @@ -1050,16 +1038,15 @@ func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.Result e.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error()) continue } - if len(tx.GetMsgs()) != 1 { - continue - } - ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - continue - } + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + continue + } - result = append(result, ethMsg) + result = append(result, ethMsg) + } } return result diff --git a/rpc/ethereum/backend/utils.go b/rpc/ethereum/backend/utils.go index c572c63ae1..dc8884eb19 100644 --- a/rpc/ethereum/backend/utils.go +++ b/rpc/ethereum/backend/utils.go @@ -182,44 +182,76 @@ func (e *EVMBackend) getAccountNonce(accAddr common.Address, pending bool, heigh // add the uncommitted txs to the nonce counter // only supports `MsgEthereumTx` style tx for _, tx := range pendingTxs { - msg, err := evmtypes.UnwrapEthereumMsg(tx) - if err != nil { - // not ethereum tx - continue + for _, msg := range (*tx).GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + // not ethereum tx + break + } + + sender, err := ethMsg.GetSender(e.chainID) + if err != nil { + continue + } + if sender == accAddr { + nonce++ + } } + } - sender, err := msg.GetSender(e.chainID) - if err != nil { + return nonce, nil +} + +// AllTxLogsFromEvents parses all ethereum logs from cosmos events +func AllTxLogsFromEvents(events []abci.Event) ([][]*ethtypes.Log, error) { + allLogs := make([][]*ethtypes.Log, 0, 4) + for _, event := range events { + if event.Type != evmtypes.EventTypeTxLog { continue } - if sender == accAddr { - nonce++ + + logs, err := ParseTxLogsFromEvent(event) + if err != nil { + return nil, err } - } - return nonce, nil + allLogs = append(allLogs, logs) + } + return allLogs, nil } -// TxLogsFromEvents parses ethereum logs from cosmos events -func TxLogsFromEvents(events []abci.Event) ([]*ethtypes.Log, error) { - logs := make([]*evmtypes.Log, 0) +// TxLogsFromEvents parses ethereum logs from cosmos events for specific msg index +func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*ethtypes.Log, error) { for _, event := range events { if event.Type != evmtypes.EventTypeTxLog { continue } - for _, attr := range event.Attributes { - if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) { - continue - } + if msgIndex > 0 { + // not the eth tx we want + msgIndex-- + continue + } - var log evmtypes.Log - if err := json.Unmarshal(attr.Value, &log); err != nil { - return nil, err - } + return ParseTxLogsFromEvent(event) + } + return nil, fmt.Errorf("eth tx logs not found for message index %d", msgIndex) +} - logs = append(logs, &log) +// ParseTxLogsFromEvent parse tx logs from one event +func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) { + logs := make([]*evmtypes.Log, 0, len(event.Attributes)) + for _, attr := range event.Attributes { + if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) { + continue } + + var log evmtypes.Log + if err := json.Unmarshal(attr.Value, &log); err != nil { + return nil, err + } + + logs = append(logs, &log) } return evmtypes.LogsToEthereum(logs), nil } diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 6608883b91..084929f6fd 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "math/big" - "strings" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -389,7 +388,20 @@ func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.Block // GetTransactionLogs returns the logs given a transaction hash. func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { e.logger.Debug("eth_getTransactionLogs", "hash", txHash) - return e.backend.GetTransactionLogs(txHash) + + hexTx := txHash.Hex() + res, err := e.backend.GetTxByEthHash(txHash) + if err != nil { + e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) + return nil, nil + } + + msgIndex, _ := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + } + // parse tx logs from events + return backend.TxLogsFromEvents(res.TxResult.Events, msgIndex) } // Sign signs the provided data using the private key of address via Geth's signature standard. @@ -556,7 +568,8 @@ func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, g } for _, tx := range pending { - p, err := evmtypes.UnwrapEthereumMsg(tx) + // FIXME does Resend api possible at all? https://github.com/tharsis/ethermint/issues/905 + p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{}) if err != nil { // not valid ethereum tx continue @@ -768,6 +781,11 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, nil } + msgIndex, attrs := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx) + } + resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height) if err != nil { e.logger.Debug("block not found", "height", res.Height, "error", err.Error()) @@ -780,13 +798,15 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, fmt.Errorf("failed to decode tx: %w", err) } - msg, err := evmtypes.UnwrapEthereumMsg(&tx) - if err != nil { - e.logger.Debug("invalid tx", "error", err.Error()) - return nil, err + // the `msgIndex` is inferred from tx events, should be within the bound. + msg := tx.GetMsgs()[msgIndex] + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + e.logger.Debug(fmt.Sprintf("invalid tx type: %T", msg)) + return nil, fmt.Errorf("invalid tx type: %T", msg) } - txData, err := evmtypes.UnpackTxData(msg.Data) + txData, err := evmtypes.UnpackTxData(ethMsg.Data) if err != nil { e.logger.Error("failed to unpack tx data", "error", err.Error()) return nil, err @@ -799,37 +819,61 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac return nil, nil } - for i := 0; i <= int(res.Index) && i < len(blockRes.TxsResults); i++ { + for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ { cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed) } + cumulativeGasUsed += rpctypes.AccumulativeGasUsedOfMsg(res.TxResult.Events, msgIndex) + + var gasUsed uint64 + if len(tx.GetMsgs()) == 1 { + // backward compatibility + gasUsed = uint64(res.TxResult.GasUsed) + } else { + gasUsed, err = rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxGasUsed) + if err != nil { + return nil, err + } + } // Get the transaction result from the log + _, found := attrs[evmtypes.AttributeKeyEthereumTxFailed] var status hexutil.Uint - if strings.Contains(res.TxResult.GetLog(), evmtypes.AttributeKeyEthereumTxFailed) { + if found { status = hexutil.Uint(ethtypes.ReceiptStatusFailed) } else { status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful) } - from, err := msg.GetSender(e.chainIDEpoch) + from, err := ethMsg.GetSender(e.chainIDEpoch) if err != nil { return nil, err } - logs, err := e.backend.GetTransactionLogs(hash) + // parse tx logs from events + logs, err := backend.TxLogsFromEvents(res.TxResult.Events, msgIndex) if err != nil { e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error()) } - // get eth index based on block's txs - var txIndex uint64 - msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes) - for i := range msgs { - if msgs[i].Hash == hexTx { - txIndex = uint64(i) - break + // Try to find txIndex from events + found = false + txIndex, err := rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex) + if err == nil { + found = true + } else { + // Fallback to find tx index by iterating all valid eth transactions + msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes) + for i := range msgs { + if msgs[i].Hash == hexTx { + txIndex = uint64(i) + found = true + break + } } } + if !found { + return nil, errors.New("can't find index of ethereum tx") + } receipt := map[string]interface{}{ // Consensus fields: These fields are defined by the Yellow Paper @@ -842,7 +886,7 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac // They are stored in the chain database. "transactionHash": hash, "contractAddress": nil, - "gasUsed": hexutil.Uint64(res.TxResult.GasUsed), + "gasUsed": hexutil.Uint64(gasUsed), "type": hexutil.Uint(txData.TxType()), // Inclusion information: These fields provide information about the inclusion of the @@ -888,24 +932,26 @@ func (e *PublicAPI) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error) result := make([]*rpctypes.RPCTransaction, 0, len(txs)) for _, tx := range txs { - msg, err := evmtypes.UnwrapEthereumMsg(tx) - if err != nil { - // not valid ethereum tx - continue - } + for _, msg := range (*tx).GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + // not valid ethereum tx + break + } - rpctx, err := rpctypes.NewTransactionFromMsg( - msg, - common.Hash{}, - uint64(0), - uint64(0), - e.chainIDEpoch, - ) - if err != nil { - return nil, err - } + rpctx, err := rpctypes.NewTransactionFromMsg( + ethMsg, + common.Hash{}, + uint64(0), + uint64(0), + e.chainIDEpoch, + ) + if err != nil { + return nil, err + } - result = append(result, rpctx) + result = append(result, rpctx) + } } return result, nil diff --git a/rpc/ethereum/namespaces/eth/filters/api.go b/rpc/ethereum/namespaces/eth/filters/api.go index 6488b6a6dd..5d6a788a1f 100644 --- a/rpc/ethereum/namespaces/eth/filters/api.go +++ b/rpc/ethereum/namespaces/eth/filters/api.go @@ -31,7 +31,6 @@ type Backend interface { GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error) BlockBloom(height *int64) (ethtypes.Bloom, error) - GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) BloomStatus() (uint64, uint64) RPCFilterCap() int32 diff --git a/rpc/ethereum/types/utils.go b/rpc/ethereum/types/utils.go index 6e8990d76c..81d84d57be 100644 --- a/rpc/ethereum/types/utils.go +++ b/rpc/ethereum/types/utils.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/hex" - "errors" "fmt" "math/big" "strconv" @@ -25,21 +24,21 @@ import ( ) // RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes. -func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) (*evmtypes.MsgEthereumTx, error) { +func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) { tx, err := clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } - if len(tx.GetMsgs()) != 1 { - return nil, errors.New("not ethereum tx") - } - - ethTx, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) - if !ok { - return nil, fmt.Errorf("invalid msg type %T, expected %T", tx, evmtypes.MsgEthereumTx{}) + ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs())) + for i, msg := range tx.GetMsgs() { + ethTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{}) + } + ethTxs[i] = ethTx } - return ethTx, nil + return ethTxs, nil } // EthHeaderFromTendermint is an util function that returns an Ethereum Header @@ -256,25 +255,80 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int { return nil } -// TxIndexFromEvents parses the tx index from cosmos events -func TxIndexFromEvents(events []abci.Event) (uint64, error) { +// FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes, +// returns -1 and nil if not found. +func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) { + msgIndex := -1 for _, event := range events { if event.Type != evmtypes.EventTypeEthereumTx { continue } + msgIndex++ + + value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash)) + if !bytes.Equal(value, []byte(txHash)) { + continue + } + + // found, convert attributes to map for later lookup + attrs := make(map[string]string, len(event.Attributes)) for _, attr := range event.Attributes { - if bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxIndex)) { - result, err := strconv.ParseInt(string(attr.Value), 10, 64) - if err != nil { - return 0, err - } - if result < 0 { - return 0, errors.New("negative tx index") - } - return uint64(result), nil - } + attrs[string(attr.Key)] = string(attr.Value) + } + return msgIndex, attrs + } + // not found + return -1, nil +} + +// FindAttribute find event attribute with specified key, if not found returns nil. +func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte { + for _, attr := range attrs { + if !bytes.Equal(attr.Key, key) { + continue + } + return attr.Value + } + return nil +} + +// GetUint64Attribute parses the uint64 value from event attributes +func GetUint64Attribute(attrs map[string]string, key string) (uint64, error) { + value, found := attrs[key] + if !found { + return 0, fmt.Errorf("tx index attribute not found: %s", key) + } + var result int64 + result, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, err + } + if result < 0 { + return 0, fmt.Errorf("negative tx index: %d", result) + } + return uint64(result), nil +} + +// AccumulativeGasUsedOfMsg accumulate the gas used by msgs before `msgIndex`. +func AccumulativeGasUsedOfMsg(events []abci.Event, msgIndex int) (gasUsed uint64) { + for _, event := range events { + if event.Type != evmtypes.EventTypeEthereumTx { + continue + } + + if msgIndex < 0 { + break + } + msgIndex-- + + value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyTxGasUsed)) + var result int64 + result, err := strconv.ParseInt(string(value), 10, 64) + if err != nil { + continue } + gasUsed += uint64(result) } - return 0, errors.New("not found") + return } diff --git a/rpc/websockets.go b/rpc/websockets.go index 1a6ef78b54..86629e3488 100644 --- a/rpc/websockets.go +++ b/rpc/websockets.go @@ -683,44 +683,46 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro select { case ev := <-txsCh: data, _ := ev.Data.(tmtypes.EventDataTx) - ethTx, err := types.RawTxToEthTx(api.clientCtx, data.Tx) + ethTxs, err := types.RawTxToEthTx(api.clientCtx, data.Tx) if err != nil { // not ethereum tx - panic("debug") + continue } api.filtersMu.RLock() - for subID, wsSub := range api.filters { - subID := subID - wsSub := wsSub - if wsSub.query != query { - continue - } - // write to ws conn - res := &SubscriptionNotification{ - Jsonrpc: "2.0", - Method: "eth_subscription", - Params: &SubscriptionResult{ - Subscription: subID, - Result: ethTx.Hash, - }, - } - - err = wsSub.wsConn.WriteJSON(res) - if err != nil { - api.logger.Debug("error writing header, will drop peer", "error", err.Error()) - - try(func() { - api.filtersMu.Lock() - defer api.filtersMu.Unlock() - - if err != websocket.ErrCloseSent { - _ = wsSub.wsConn.Close() - } - - delete(api.filters, subID) - close(wsSub.unsubscribed) - }, api.logger, "closing websocket peer sub") + for _, ethTx := range ethTxs { + for subID, wsSub := range api.filters { + subID := subID + wsSub := wsSub + if wsSub.query != query { + continue + } + // write to ws conn + res := &SubscriptionNotification{ + Jsonrpc: "2.0", + Method: "eth_subscription", + Params: &SubscriptionResult{ + Subscription: subID, + Result: ethTx.Hash, + }, + } + + err = wsSub.wsConn.WriteJSON(res) + if err != nil { + api.logger.Debug("error writing header, will drop peer", "error", err.Error()) + + try(func() { + api.filtersMu.Lock() + defer api.filtersMu.Unlock() + + if err != websocket.ErrCloseSent { + _ = wsSub.wsConn.Close() + } + + delete(api.filters, subID) + close(wsSub.unsubscribed) + }, api.logger, "closing websocket peer sub") + } } } api.filtersMu.RUnlock() diff --git a/x/evm/client/rest/rest.go b/x/evm/client/rest/rest.go index 81368e29f9..73e410fb74 100644 --- a/x/evm/client/rest/rest.go +++ b/x/evm/client/rest/rest.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "encoding/json" + "errors" "math/big" "net/http" "strings" @@ -94,17 +95,22 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte, blockHash := common.BytesToHash(block.Block.Header.Hash()) - ethTx, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx) + ethTxs, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx) if err != nil { return nil, err } height := uint64(tx.Height) - rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee) - if err != nil { - return nil, err + for _, ethTx := range ethTxs { + if common.HexToHash(ethTx.Hash) == common.BytesToHash(hash) { + rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee) + if err != nil { + return nil, err + } + return json.Marshal(rpcTx) + } } - return json.Marshal(rpcTx) + return nil, errors.New("eth tx not found") } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index c9ebd8abf9..c6903eec54 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -301,3 +302,36 @@ func (k Keeper) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int { } return baseFee } + +// ResetTransientGasUsed reset gas used to prepare for execution of current cosmos tx, called in ante handler. +func (k Keeper) ResetTransientGasUsed(ctx sdk.Context) { + store := ctx.TransientStore(k.transientKey) + store.Delete(types.KeyPrefixTransientGasUsed) +} + +// GetTransientGasUsed returns the gas used by current cosmos tx. +func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 { + store := ctx.TransientStore(k.transientKey) + bz := store.Get(types.KeyPrefixTransientGasUsed) + if len(bz) == 0 { + return 0 + } + return sdk.BigEndianToUint64(bz) +} + +// SetTransientGasUsed sets the gas used by current cosmos tx. +func (k Keeper) SetTransientGasUsed(ctx sdk.Context, gasUsed uint64) { + store := ctx.TransientStore(k.transientKey) + bz := sdk.Uint64ToBigEndian(gasUsed) + store.Set(types.KeyPrefixTransientGasUsed, bz) +} + +// AddTransientGasUsed accumulate gas used by each eth msgs included in current cosmos tx. +func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, error) { + result := k.GetTransientGasUsed(ctx) + gasUsed + if result < gasUsed { + return 0, sdkerrors.Wrap(types.ErrGasOverflow, "transient gas used") + } + k.SetTransientGasUsed(ctx, result) + return result, nil +} diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index f3cfe68e5b..50c49afc2d 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -38,7 +38,9 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t // add event for ethereum transaction hash format sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash), // add event for index of valid ethereum tx - sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatInt(int64(txIndex), 10)), + sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)), + // add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs. + sdk.NewAttribute(types.AttributeKeyTxGasUsed, strconv.FormatUint(response.GasUsed, 10)), } if len(ctx.TxBytes()) > 0 { diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 1560b6168b..10b66aacf7 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -287,8 +287,13 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1) - // update the gas used after refund - k.ResetGasMeterAndConsumeGas(ctx, res.GasUsed) + totalGasUsed, err := k.AddTransientGasUsed(ctx, res.GasUsed) + if err != nil { + return nil, sdkerrors.Wrap(err, "failed to add transient gas used") + } + + // reset the gas meter for current cosmos transaction + k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed) return res, nil } diff --git a/x/evm/spec/02_state.md b/x/evm/spec/02_state.md index 800917f724..7b6d7197e8 100644 --- a/x/evm/spec/02_state.md +++ b/x/evm/spec/02_state.md @@ -19,6 +19,7 @@ The `x/evm` module keeps the following objects in state: | Block Bloom | Block bloom filter, used to accumulate the bloom filter of current block, emitted to events at end blocker. | `[]byte{1} + []byte(tx.Hash)` | `protobuf([]Log)` | Transient | | Tx Index | Index of current transaction in current block. | `[]byte{2}` | `BigEndian(uint64)` | Transient | | Log Size | Number of the logs emitted so far in current block. Used to decide the log index of following logs. | `[]byte{3}` | `BigEndian(uint64)` | Transient | +| Gas Used | Amount of gas used by ethereum messages of current cosmos-sdk tx, it's necessary when cosmos-sdk tx contains multiple ethereum messages. | `[]byte{4}` | `BigEndian(uint64)` | Transient | ## StateDB @@ -161,7 +162,7 @@ With `AddLog()` you can append the given ethereum `Log` to the list of Logs asso ## Keeper -The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions. +The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions. To support the interface functionality, it imports 4 module Keepers: diff --git a/x/evm/spec/07_events.md b/x/evm/spec/07_events.md index dc3e6a0a5e..0a749899db 100644 --- a/x/evm/spec/07_events.md +++ b/x/evm/spec/07_events.md @@ -16,6 +16,7 @@ The `x/evm` module emits the Cosmos SDK events after a state execution. The EVM | ethereum_tx | `"txHash"` | `{tendermint_hex_hash}` | | ethereum_tx | `"ethereumTxHash"` | `{hex_hash}` | | ethereum_tx | `"txIndex"` | `{tx_index}` | +| ethereum_tx | `"txGasUsed"` | `{gas_used}` | | tx_log | `"txLog"` | `{tx_log}` | | message | `"sender"` | `{eth_address}` | | message | `"action"` | `"ethereum"` | diff --git a/x/evm/types/events.go b/x/evm/types/events.go index 0b8ca69a94..d9e8592892 100644 --- a/x/evm/types/events.go +++ b/x/evm/types/events.go @@ -11,6 +11,7 @@ const ( AttributeKeyTxHash = "txHash" AttributeKeyEthereumTxHash = "ethereumTxHash" AttributeKeyTxIndex = "txIndex" + AttributeKeyTxGasUsed = "txGasUsed" AttributeKeyTxType = "txType" AttributeKeyTxLog = "txLog" // tx failed in eth vm execution diff --git a/x/evm/types/key.go b/x/evm/types/key.go index 36e5a8fcbb..ade14eb20a 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -32,6 +32,7 @@ const ( prefixTransientBloom = iota + 1 prefixTransientTxIndex prefixTransientLogSize + prefixTransientGasUsed ) // KVStore key prefixes @@ -45,6 +46,7 @@ var ( KeyPrefixTransientBloom = []byte{prefixTransientBloom} KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex} KeyPrefixTransientLogSize = []byte{prefixTransientLogSize} + KeyPrefixTransientGasUsed = []byte{prefixTransientGasUsed} ) // AddressStoragePrefix returns a prefix to iterate over a given account storage. diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 94b7192f4c..16b1c78068 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -54,20 +55,22 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) { } // UnwrapEthereumMsg extract MsgEthereumTx from wrapping sdk.Tx -func UnwrapEthereumMsg(tx *sdk.Tx) (*MsgEthereumTx, error) { +func UnwrapEthereumMsg(tx *sdk.Tx, ethHash common.Hash) (*MsgEthereumTx, error) { if tx == nil { return nil, fmt.Errorf("invalid tx: nil") } - if len((*tx).GetMsgs()) != 1 { - return nil, fmt.Errorf("invalid tx type: %T", tx) - } - msg, ok := (*tx).GetMsgs()[0].(*MsgEthereumTx) - if !ok { - return nil, fmt.Errorf("invalid tx type: %T", tx) + for _, msg := range (*tx).GetMsgs() { + ethMsg, ok := msg.(*MsgEthereumTx) + if !ok { + return nil, fmt.Errorf("invalid tx type: %T", tx) + } + if ethMsg.AsTransaction().Hash() == ethHash { + return ethMsg, nil + } } - return msg, nil + return nil, fmt.Errorf("eth tx not found: %s", ethHash) } // BinSearch execute the binary search and hone in on an executable gas limit diff --git a/x/evm/types/utils_test.go b/x/evm/types/utils_test.go index 21aac53209..d3ec00e565 100644 --- a/x/evm/types/utils_test.go +++ b/x/evm/types/utils_test.go @@ -48,7 +48,7 @@ func TestEvmDataEncoding(t *testing.T) { } func TestUnwrapEthererumMsg(t *testing.T) { - _, err := evmtypes.UnwrapEthereumMsg(nil) + _, err := evmtypes.UnwrapEthereumMsg(nil, common.Hash{}) require.NotNil(t, err) encodingConfig := encoding.MakeConfig(app.ModuleBasics) @@ -56,14 +56,14 @@ func TestUnwrapEthererumMsg(t *testing.T) { builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) tx := builder.GetTx().(sdk.Tx) - _, err = evmtypes.UnwrapEthereumMsg(&tx) + _, err = evmtypes.UnwrapEthereumMsg(&tx, common.Hash{}) require.NotNil(t, err) msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil, nil, []byte{}, nil) err = builder.SetMsgs(msg) tx = builder.GetTx().(sdk.Tx) - msg_, err := evmtypes.UnwrapEthereumMsg(&tx) + msg_, err := evmtypes.UnwrapEthereumMsg(&tx, msg.AsTransaction().Hash()) require.Nil(t, err) require.Equal(t, msg_, msg) } From 0b92af406ac20ec79ad875dde18a60094abfc283 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jan 2022 16:27:21 +0100 Subject: [PATCH 04/12] build(deps): bump follow-redirects in /tests/solidity (#909) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.4 to 1.14.7. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.4...v1.14.7) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/solidity/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/solidity/yarn.lock b/tests/solidity/yarn.lock index 08e54e5ff9..52b52aac59 100644 --- a/tests/solidity/yarn.lock +++ b/tests/solidity/yarn.lock @@ -7636,9 +7636,9 @@ flow-stoplight@^1.0.0: integrity sha1-SiksW8/4s5+mzAyxqFPYbyfu/3s= follow-redirects@^1.10.0: - version "1.14.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" - integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" From 351e6d6eb3f23f72a8e2566fed86769f0ba1f3df Mon Sep 17 00:00:00 2001 From: yihuang Date: Sun, 16 Jan 2022 23:36:19 +0800 Subject: [PATCH 05/12] rpc: make trace transaction api work with batch tx (#907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make trace transaction api work with batch tx Closes: #906 fix linter * review suggestion Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- rpc/ethereum/namespaces/debug/api.go | 47 +++++++++++++++++----------- rpc/ethereum/namespaces/eth/api.go | 7 +++-- rpc/ethereum/types/utils.go | 25 +++++++++++++++ 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/rpc/ethereum/namespaces/debug/api.go b/rpc/ethereum/namespaces/debug/api.go index ad516afefc..5dacaeb2aa 100644 --- a/rpc/ethereum/namespaces/debug/api.go +++ b/rpc/ethereum/namespaces/debug/api.go @@ -89,13 +89,17 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( return nil, err } + msgIndex, _ := rpctypes.FindTxAttributes(transaction.TxResult.Events, hash.Hex()) + if msgIndex < 0 { + return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex()) + } + // check tx index is not out of bound if uint32(len(blk.Block.Txs)) < transaction.Index { a.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height) return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height) } - // nolint: prealloc var predecessors []*evmtypes.MsgEthereumTx for _, txBz := range blk.Block.Txs[:transaction.Index] { tx, err := a.clientCtx.TxConfig.TxDecoder()(txBz) @@ -103,13 +107,14 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( a.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error()) continue } - msg := tx.GetMsgs()[0] - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - continue - } + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + continue + } - predecessors = append(predecessors, ethMsg) + predecessors = append(predecessors, ethMsg) + } } tx, err := a.clientCtx.TxConfig.TxDecoder()(transaction.Tx) @@ -118,7 +123,16 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( return nil, err } - ethMessage, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + // add predecessor messages in current cosmos tx + for i := 0; i < msgIndex; i++ { + ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx) + if !ok { + continue + } + predecessors = append(predecessors, ethMsg) + } + + ethMessage, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx) if !ok { a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx)) return nil, fmt.Errorf("invalid transaction type %T", tx) @@ -209,7 +223,6 @@ func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConf txDecoder := a.clientCtx.TxConfig.TxDecoder() - // nolint: prealloc var txsMessages []*evmtypes.MsgEthereumTx for i, tx := range txs { decodedTx, err := txDecoder(tx) @@ -218,16 +231,14 @@ func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConf continue } - messages := decodedTx.GetMsgs() - if len(messages) == 0 { - continue - } - ethMessage, ok := messages[0].(*evmtypes.MsgEthereumTx) - if !ok { - // Just considers Ethereum transactions - continue + for _, msg := range decodedTx.GetMsgs() { + ethMessage, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + // Just considers Ethereum transactions + continue + } + txsMessages = append(txsMessages, ethMessage) } - txsMessages = append(txsMessages, ethMessage) } // minus one to get the context at the beginning of the block diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 084929f6fd..2cac0699d8 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -699,12 +699,15 @@ func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil } - if len(tx.GetMsgs()) != 1 { + // find msg index in events + msgIndex := rpctypes.FindTxAttributesByIndex(res.TxResult.Events, uint64(idx)) + if msgIndex < 0 { e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil } var ok bool - msg, ok = tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) + // msgIndex is inferred from tx events, should be within bound. + msg, ok = tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx) if !ok { e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx) return nil, nil diff --git a/rpc/ethereum/types/utils.go b/rpc/ethereum/types/utils.go index 81d84d57be..31abc28645 100644 --- a/rpc/ethereum/types/utils.go +++ b/rpc/ethereum/types/utils.go @@ -282,6 +282,31 @@ func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]strin return -1, nil } +// FindTxAttributesByIndex search the msg in tx events by txIndex +// returns the msgIndex, returns -1 if not found. +func FindTxAttributesByIndex(events []abci.Event, txIndex uint64) int { + strIndex := []byte(strconv.FormatUint(txIndex, 10)) + txIndexKey := []byte(evmtypes.AttributeKeyTxIndex) + msgIndex := -1 + for _, event := range events { + if event.Type != evmtypes.EventTypeEthereumTx { + continue + } + + msgIndex++ + + value := FindAttribute(event.Attributes, txIndexKey) + if !bytes.Equal(value, strIndex) { + continue + } + + // found, convert attributes to map for later lookup + return msgIndex + } + // not found + return -1 +} + // FindAttribute find event attribute with specified key, if not found returns nil. func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte { for _, attr := range attrs { From 317f5b43b516022da831708704a40cb8af79e87a Mon Sep 17 00:00:00 2001 From: yihuang Date: Mon, 17 Jan 2022 14:12:13 +0800 Subject: [PATCH 06/12] fix insufficient fee error message (#911) --- app/ante/eth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ante/eth.go b/app/ante/eth.go index 9f74039c7d..ae4dce5a57 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -286,7 +286,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate } if coreMsg.GasFeeCap().Cmp(baseFee) < 0 { return ctx, sdkerrors.Wrapf( - evmtypes.ErrInvalidBaseFee, + sdkerrors.ErrInsufficientFee, "max fee per gas less than block base fee (%s < %s)", coreMsg.GasFeeCap(), baseFee, ) From 0a01cead672e32a7c8d160821bdd19ab94239cb7 Mon Sep 17 00:00:00 2001 From: crypto-facs <84574577+crypto-facs@users.noreply.github.com> Date: Mon, 17 Jan 2022 13:08:02 -0500 Subject: [PATCH 07/12] server: add `api.enable` flag for Cosmos SDK API server (#908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add api.enable flag for Cosmos SDK Rest server * update changelog Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- CHANGELOG.md | 1 + init.sh | 2 +- server/flags/flags.go | 6 +++++- server/start.go | 2 ++ tests/solidity/init-test-node.sh | 2 +- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86fdfdd503..9910f0f87b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (ante) [tharsis#819](https://github.com/tharsis/ethermint/pull/819) remove redundant ante handlers * (app) [tharsis#873](https://github.com/tharsis/ethermint/pull/873) Validate code hash in GenesisAccount * (evm) [tharsis#901](https://github.com/tharsis/ethermint/pull/901) Support multiple MsgEthereumTx in single tx. +* (config) [tharsis#908](https://github.com/tharsis/ethermint/pull/908) Add api.enable flag for Cosmos SDK Rest server ### Bug Fixes diff --git a/init.sh b/init.sh index 5b80f348f0..de11d28139 100755 --- a/init.sh +++ b/init.sh @@ -87,4 +87,4 @@ if [[ $1 == "pending" ]]; then fi # Start the node (remove the --pruning=nothing flag if historical queries are not needed) -ethermintd start --pruning=nothing --evm.tracer=json $TRACE --log_level $LOGLEVEL --minimum-gas-prices=0.0001aphoton --json-rpc.api eth,txpool,personal,net,debug,web3,miner +ethermintd start --pruning=nothing --evm.tracer=json $TRACE --log_level $LOGLEVEL --minimum-gas-prices=0.0001aphoton --json-rpc.api eth,txpool,personal,net,debug,web3,miner --api.enable diff --git a/server/flags/flags.go b/server/flags/flags.go index 9d797854ef..e213f5bb17 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -24,6 +24,11 @@ const ( GRPCWebAddress = "grpc-web.address" ) +// RPCEnable Defines if Cosmos-sdk REST server should be enabled +const ( + RPCEnable = "api.enable" +) + // JSON-RPC flags const ( JSONRPCEnable = "json-rpc.enable" @@ -34,7 +39,6 @@ const ( JSONRPCEVMTimeout = "json-rpc.evm-timeout" JSONRPCTxFeeCap = "json-rpc.txfee-cap" JSONRPCFilterCap = "json-rpc.filter-cap" - JSONRPFeeHistoryCap = "json-rpc.feehistory-cap" JSONRPCLogsCap = "json-rpc.logs-cap" JSONRPCBlockRangeCap = "json-rpc.block-range-cap" ) diff --git a/server/start.go b/server/start.go index 4ccd936ee1..97f1d33aae 100644 --- a/server/start.go +++ b/server/start.go @@ -149,6 +149,8 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(srvflags.GRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled.)") cmd.Flags().String(srvflags.GRPCWebAddress, serverconfig.DefaultGRPCWebAddress, "The gRPC-Web server address to listen on") + cmd.Flags().Bool(srvflags.RPCEnable, false, "Defines if Cosmos-sdk REST server should be enabled") + cmd.Flags().Bool(srvflags.JSONRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().StringSlice(srvflags.JSONRPCAPI, config.GetDefaultAPINamespaces(), "Defines a list of JSON-RPC namespaces that should be enabled") cmd.Flags().String(srvflags.JSONRPCAddress, config.DefaultJSONRPCAddress, "the JSON-RPC server address to listen on") diff --git a/tests/solidity/init-test-node.sh b/tests/solidity/init-test-node.sh index 7e1f7981b7..6f0cf15c6e 100755 --- a/tests/solidity/init-test-node.sh +++ b/tests/solidity/init-test-node.sh @@ -58,4 +58,4 @@ ethermintd collect-gentxs ethermintd validate-genesis # Start the node (remove the --pruning=nothing flag if historical queries are not needed) -ethermintd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 +ethermintd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable From 43c4d7c3fb53b7f9cd155dcd1553cec43ff5fc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= <31522760+fedekunze@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:02:10 +0100 Subject: [PATCH 08/12] deps: bump Cosmos SDK version to `v0.45.0` (#912) * deps: bump SDK version to v0.45.0 * changelog * deprecation fix * deprecation fix 2 --- CHANGELOG.md | 1 + client/testnet.go | 38 ++----------------------------------- go.mod | 4 +--- go.sum | 4 ++-- testutil/network/network.go | 3 ++- 5 files changed, 8 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9910f0f87b..a99751bf4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### State Machine Breaking +* (deps) [tharis#912](https://github.com/tharsis/ethermint/pull/912) Bump Cosmos SDK version to [`v0.45.0`](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.0) * (evm) [tharsis#840](https://github.com/tharsis/ethermint/pull/840) Store empty topics as empty array rather than nil. * (feemarket) [tharsis#822](https://github.com/tharsis/ethermint/pull/822) Update EIP1559 base fee in `BeginBlock`. * (evm) [tharsis#817](https://github.com/tharsis/ethermint/pull/817) Use `effectiveGasPrice` in ante handler, add `effectiveGasPrice` to tx receipt. diff --git a/client/testnet.go b/client/testnet.go index fb4e5e106b..4bee072e9f 100644 --- a/client/testnet.go +++ b/client/testnet.go @@ -25,6 +25,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdkserver "github.com/cosmos/cosmos-sdk/server" srvconfig "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -272,8 +273,7 @@ func initTestnetFiles( return err } - // TODO: remove when using Cosmos SDK v0.45 - addr, secret, err := GenerateSaveCoinKey(kb, nodeDirName, true, algo) + addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo) if err != nil { _ = os.RemoveAll(args.outputDir) return err @@ -564,37 +564,3 @@ func startTestnet(cmd *cobra.Command, args startArgs) error { return nil } - -// TODO: remove - -// GenerateSaveCoinKey returns the address of a public key, along with the secret -// phrase to recover the private key. -func GenerateSaveCoinKey(keybase keyring.Keyring, keyName string, overwrite bool, algo keyring.SignatureAlgo) (sdk.AccAddress, string, error) { - exists := false - _, err := keybase.Key(keyName) - if err == nil { - exists = true - } - - // ensure no overwrite - if !overwrite && exists { - return sdk.AccAddress([]byte{}), "", fmt.Errorf( - "key already exists, overwrite is disabled") - } - - // generate a private key, with recovery phrase - if exists { - err = keybase.Delete(keyName) - if err != nil { - return sdk.AccAddress([]byte{}), "", fmt.Errorf( - "failed to overwrite key") - } - } - - info, secret, err := keybase.NewMnemonic(keyName, keyring.English, sdk.GetConfig().GetFullBIP44Path(), keyring.DefaultBIP39Passphrase, algo) - if err != nil { - return sdk.AccAddress([]byte{}), "", err - } - - return sdk.AccAddress(info.GetPubKey().Address()), secret, nil -} diff --git a/go.mod b/go.mod index 6b542d6760..1359d7f775 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/btcsuite/btcd v0.22.0-beta github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce - github.com/cosmos/cosmos-sdk v0.44.5 + github.com/cosmos/cosmos-sdk v0.45.0 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-go/v3 v3.0.0-alpha2 github.com/davecgh/go-spew v1.1.1 @@ -152,8 +152,6 @@ require ( replace ( github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 - // TODO: remove once v0.45 is released - github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 google.golang.org/grpc => google.golang.org/grpc v1.33.2 ) diff --git a/go.sum b/go.sum index 75f5c4bc19..ae0b3bc1e0 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1 h1:nN+l89gOFpd6ipMR6N/3qu+p/T4me2xMU1P6jc0zbQ8= -github.com/cosmos/cosmos-sdk v0.44.6-0.20220104141845-0be9863cfed1/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= +github.com/cosmos/cosmos-sdk v0.45.0 h1:DHD+CIRZ+cYgiLXuTEUL/aprnfPsWSwaww/fIZEsZlk= +github.com/cosmos/cosmos-sdk v0.45.0/go.mod h1:XXS/asyCqWNWkx2rW6pSuen+EVcpAFxq6khrhnZgHaQ= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= diff --git a/testutil/network/network.go b/testutil/network/network.go index 8d7a9151ae..5d23a55e79 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -41,6 +41,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/simapp/params" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -380,7 +381,7 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { return nil, err } - addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo) + addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo) if err != nil { return nil, err } From 04f595d3d5aeda12e4b54a0ca010aa2db4392ca3 Mon Sep 17 00:00:00 2001 From: crypto-facs <84574577+crypto-facs@users.noreply.github.com> Date: Thu, 20 Jan 2022 17:05:02 -0500 Subject: [PATCH 09/12] Integration tests (#913) --- tests/e2e/integration_test.go | 419 +++++++++++++++++++- tests/rpc/rpc_test.go | 510 +------------------------ x/evm/types/SimpleStorageContract.json | 4 + x/evm/types/compiled_contract.go | 15 + 4 files changed, 435 insertions(+), 513 deletions(-) create mode 100644 x/evm/types/SimpleStorageContract.json diff --git a/tests/e2e/integration_test.go b/tests/e2e/integration_test.go index 4ca6e4b3a6..773f95acc0 100644 --- a/tests/e2e/integration_test.go +++ b/tests/e2e/integration_test.go @@ -1,9 +1,11 @@ package e2e_test import ( + "bytes" "context" "fmt" - "github.com/ethereum/go-ethereum/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + evmtypes "github.com/tharsis/ethermint/x/evm/types" "math/big" "testing" @@ -42,6 +44,7 @@ type IntegrationTestSuite struct { network *network.Network gethClient *gethclient.Client + ethSigner ethtypes.Signer } func (s *IntegrationTestSuite) SetupSuite() { @@ -72,6 +75,9 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) s.gethClient = gethclient.New(rpcClient) s.Require().NotNil(s.gethClient) + chainId, err := ethermint.ParseChainID(s.cfg.ChainID) + s.Require().NoError(err) + s.ethSigner = ethtypes.LatestSignerForChainID(chainId) } func (s *IntegrationTestSuite) TestChainID() { @@ -139,6 +145,20 @@ func (s *IntegrationTestSuite) TestBlock() { // TODO: parse Tm block to Ethereum and compare } +func (s *IntegrationTestSuite) TestBlockBloom() { + transactionHash, _ := s.deployTestContract() + receipt, err := s.network.Validators[0].JSONRPCClient.TransactionReceipt(s.ctx, transactionHash) + s.Require().NoError(err) + + number := receipt.BlockNumber + block, err := s.network.Validators[0].JSONRPCClient.BlockByNumber(s.ctx, number) + s.Require().NoError(err) + + lb := block.Bloom().Big() + s.Require().NotEqual(big.NewInt(0), lb) + s.Require().Equal(transactionHash.String(), block.Transactions()[0].Hash().String()) +} + func (s *IntegrationTestSuite) TestHeader() { blockNum, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) s.Require().NoError(err) @@ -263,14 +283,403 @@ func (s *IntegrationTestSuite) TestSendTransactionContractDeploymentNoGas() { var data hexutil.Bytes err := data.UnmarshalText([]byte(bytecode)) - var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - tx := ethtypes.NewContractCreation(0, nil, 0x5208, nil, data) - tx, _ = ethtypes.SignTx(tx, ethtypes.HomesteadSigner{}, testKey) + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) - err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, tx) + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + contractDeployTx := evmtypes.NewTxContract( + chainID, + nonce, + nil, // amount + 0x5208, // gasLimit + nil, // gasPrice + nil, nil, + data, // input + nil, // accesses + ) + contractDeployTx.From = owner.Hex() + err = contractDeployTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, contractDeployTx.AsTransaction()) s.Require().Error(err) } +func (s *IntegrationTestSuite) TestBlockTransactionCount() { + // start with clean block + err := s.network.WaitForNextBlock() + s.Require().NoError(err) + + signedTx := s.signValidTx(common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(10)) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, signedTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + receipt := s.expectSuccessReceipt(signedTx.AsTransaction().Hash()) + // TransactionCount endpoint represents eth_getTransactionCountByHash + count, err := s.network.Validators[0].JSONRPCClient.TransactionCount(s.ctx, receipt.BlockHash) + s.Require().NoError(err) + s.Require().Equal(uint(1), count) + + // expect 0 response with random block hash + anyBlockHash := common.HexToHash("0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35") + count, err = s.network.Validators[0].JSONRPCClient.TransactionCount(s.ctx, anyBlockHash) + s.Require().NoError(err) + s.Require().NotEqual(uint(0), 0) +} + +func (s *IntegrationTestSuite) TestGetTransactionByBlockHashAndIndex() { + signedTx := s.signValidTx(common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(10)) + err := s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, signedTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + receipt := s.expectSuccessReceipt(signedTx.AsTransaction().Hash()) + + // TransactionInBlock endpoint represents eth_getTransactionByBlockHashAndIndex + transaction, err := s.network.Validators[0].JSONRPCClient.TransactionInBlock(s.ctx, receipt.BlockHash, 0) + s.Require().NoError(err) + s.Require().NotNil(transaction) + s.Require().Equal(receipt.TxHash, transaction.Hash()) +} + +func (s *IntegrationTestSuite) TestGetBalance() { + blockNumber, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + + initialBalance, err := s.network.Validators[0].JSONRPCClient.BalanceAt(s.ctx, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), big.NewInt(int64(blockNumber))) + s.Require().NoError(err) + + amountToTransfer := big.NewInt(10) + signedTx := s.signValidTx(common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), amountToTransfer) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, signedTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + receipt := s.expectSuccessReceipt(signedTx.AsTransaction().Hash()) + finalBalance, err := s.network.Validators[0].JSONRPCClient.BalanceAt(s.ctx, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), receipt.BlockNumber) + s.Require().NoError(err) + + var result big.Int + s.Require().Equal(result.Add(initialBalance, amountToTransfer), finalBalance) + + // test old balance is still the same + prevBalance, err := s.network.Validators[0].JSONRPCClient.BalanceAt(s.ctx, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ed"), big.NewInt(int64(blockNumber))) + s.Require().NoError(err) + s.Require().Equal(initialBalance, prevBalance) +} + +func (s *IntegrationTestSuite) TestGetLogs() { + //TODO create tests to cover different filterQuery params + _, contractAddr := s.deployERC20Contract() + + blockNum, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + + s.transferERC20Transaction(contractAddr, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(10)) + filterQuery := ethereum.FilterQuery{ + FromBlock: big.NewInt(int64(blockNum)), + } + + logs, err := s.network.Validators[0].JSONRPCClient.FilterLogs(s.ctx, filterQuery) + s.Require().NoError(err) + s.Require().NotNil(logs) + s.Require().Equal(1, len(logs)) + + expectedTopics := []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + common.HexToHash("0x000000000000000000000000" + fmt.Sprintf("%x", common.BytesToAddress(s.network.Validators[0].Address))), + common.HexToHash("0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"), + } + s.Require().Equal(expectedTopics, logs[0].Topics) +} + +func (s *IntegrationTestSuite) TestTransactionReceiptERC20Transfer() { + //start with clean block + err := s.network.WaitForNextBlock() + s.Require().NoError(err) + // deploy erc20 contract + _, contractAddr := s.deployERC20Contract() + + amount := big.NewInt(10) + hash := s.transferERC20Transaction(contractAddr, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), amount) + transferReceipt := s.expectSuccessReceipt(hash) + logs := transferReceipt.Logs + s.Require().Equal(1, len(logs)) + s.Require().Equal(contractAddr, logs[0].Address) + + s.Require().Equal(amount, big.NewInt(0).SetBytes(logs[0].Data)) + + s.Require().Equal(false, logs[0].Removed) + s.Require().Equal(uint(0x0), logs[0].Index) + s.Require().Equal(uint(0x0), logs[0].TxIndex) + + expectedTopics := []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + common.HexToHash("0x000000000000000000000000" + fmt.Sprintf("%x", common.BytesToAddress(s.network.Validators[0].Address))), + common.HexToHash("0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"), + } + s.Require().Equal(expectedTopics, logs[0].Topics) +} + +func (s *IntegrationTestSuite) TestGetCode() { + expectedCode := "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80636d4ce63c1461003b578063d04ad49514610059575b600080fd5b610043610075565b6040516100509190610132565b60405180910390f35b610073600480360381019061006e91906100f6565b61009e565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000813590506100f081610172565b92915050565b60006020828403121561010c5761010b61016d565b5b600061011a848285016100e1565b91505092915050565b61012c8161014d565b82525050565b60006020820190506101476000830184610123565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600080fd5b61017b8161014d565b811461018657600080fd5b5056fea26469706673582212204c98c8f28598d29acc328cb34578de54cbed70b20bf9364897d48b2381f0c78b64736f6c63430008070033" + + _, addr := s.deploySimpleStorageContract() + block, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + code, err := s.network.Validators[0].JSONRPCClient.CodeAt(s.ctx, addr, big.NewInt(int64(block))) + s.Require().NoError(err) + s.Require().Equal(expectedCode, hexutil.Encode(code)) +} + +func (s *IntegrationTestSuite) TestGetStorageAt() { + expectedStore := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5} + _, addr := s.deploySimpleStorageContract() + + s.storeValueStorageContract(addr, big.NewInt(5)) + block, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx) + s.Require().NoError(err) + + storage, err := s.network.Validators[0].JSONRPCClient.StorageAt(s.ctx, addr, common.BigToHash(big.NewInt(0)), big.NewInt(int64(block))) + s.Require().NoError(err) + s.Require().NotNil(storage) + s.Require().True(bytes.Equal(expectedStore, storage)) +} + +func (s *IntegrationTestSuite) getGasPrice() *big.Int { + gasPrice, err := s.network.Validators[0].JSONRPCClient.SuggestGasPrice(s.ctx) + s.Require().NoError(err) + return gasPrice +} + +func (s *IntegrationTestSuite) getAccountNonce(addr common.Address) uint64 { + nonce, err := s.network.Validators[0].JSONRPCClient.NonceAt(s.ctx, addr, nil) + s.Require().NoError(err) + return nonce +} + +func (s *IntegrationTestSuite) signValidTx(to common.Address, amount *big.Int) *evmtypes.MsgEthereumTx { + chainId, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + from := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(from) + + msgTx := evmtypes.NewTx( + chainId, + nonce, + &to, + amount, + 100000, + gasPrice, + big.NewInt(200), + nil, + nil, + nil, + ) + msgTx.From = from.Hex() + err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + return msgTx +} + +func (s *IntegrationTestSuite) signValidContractDeploymentTx(input []byte) *evmtypes.MsgEthereumTx { + chainId, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + from := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(from) + + msgTx := evmtypes.NewTxContract( + chainId, + nonce, + big.NewInt(10), + 134216, + gasPrice, + big.NewInt(200), + nil, + input, + nil, + ) + msgTx.From = from.Hex() + err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + return msgTx +} + +func (s *IntegrationTestSuite) deployTestContract() (transaction common.Hash, contractAddr common.Address) { + bytecode := "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" + + var data hexutil.Bytes + err := data.UnmarshalText([]byte(bytecode)) + s.Require().NoError(err) + + return s.deployContract(data) +} + +func (s *IntegrationTestSuite) deployContract(data []byte) (transaction common.Hash, contractAddr common.Address) { + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + + gas, err := s.network.Validators[0].JSONRPCClient.EstimateGas(s.ctx, ethereum.CallMsg{ + From: owner, + Data: data, + }) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + + contractDeployTx := evmtypes.NewTxContract( + chainID, + nonce, + nil, // amount + gas, // gasLimit + gasPrice, // gasPrice + nil, nil, + data, // input + nil, // accesses + ) + + contractDeployTx.From = owner.Hex() + err = contractDeployTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, contractDeployTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + + receipt := s.expectSuccessReceipt(contractDeployTx.AsTransaction().Hash()) + s.Require().NotNil(receipt.ContractAddress) + return contractDeployTx.AsTransaction().Hash(), receipt.ContractAddress +} + +// Deploys erc20 contract, commits block and returns contract address +func (s *IntegrationTestSuite) deployERC20Contract() (transaction common.Hash, contractAddr common.Address) { + owner := common.BytesToAddress(s.network.Validators[0].Address) + supply := sdk.NewIntWithDecimal(1000, 18).BigInt() + + ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", owner, supply) + s.Require().NoError(err) + + data := append(evmtypes.ERC20Contract.Bin, ctorArgs...) + return s.deployContract(data) +} + +// Deploys SimpleStorageContract and,commits block and returns contract address +func (s *IntegrationTestSuite) deploySimpleStorageContract() (transaction common.Hash, contractAddr common.Address) { + ctorArgs, err := evmtypes.SimpleStorageContract.ABI.Pack("") + s.Require().NoError(err) + + data := append(evmtypes.SimpleStorageContract.Bin, ctorArgs...) + return s.deployContract(data) +} + +func (s *IntegrationTestSuite) expectSuccessReceipt(hash common.Hash) *ethtypes.Receipt { + receipt, err := s.network.Validators[0].JSONRPCClient.TransactionReceipt(s.ctx, hash) + s.Require().NoError(err) + s.Require().NotNil(receipt) + s.Require().Equal(uint64(0x1), receipt.Status) + return receipt +} + +func (s *IntegrationTestSuite) transferERC20Transaction(contractAddr, to common.Address, amount *big.Int) common.Hash { + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + transferData, err := evmtypes.ERC20Contract.ABI.Pack("transfer", to, amount) + s.Require().NoError(err) + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + + gas, err := s.network.Validators[0].JSONRPCClient.EstimateGas(s.ctx, ethereum.CallMsg{ + To: &contractAddr, + From: owner, + Data: transferData, + }) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + ercTransferTx := evmtypes.NewTx( + chainID, + nonce, + &contractAddr, + nil, + gas, + gasPrice, + nil, nil, + transferData, + nil, + ) + + ercTransferTx.From = owner.Hex() + err = ercTransferTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, ercTransferTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + + receipt := s.expectSuccessReceipt(ercTransferTx.AsTransaction().Hash()) + s.Require().NotEmpty(receipt.Logs) + return ercTransferTx.AsTransaction().Hash() + +} + +func (s *IntegrationTestSuite) storeValueStorageContract(contractAddr common.Address, amount *big.Int) common.Hash { + chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx) + s.Require().NoError(err) + + transferData, err := evmtypes.SimpleStorageContract.ABI.Pack("store", amount) + s.Require().NoError(err) + owner := common.BytesToAddress(s.network.Validators[0].Address) + nonce := s.getAccountNonce(owner) + + gas, err := s.network.Validators[0].JSONRPCClient.EstimateGas(s.ctx, ethereum.CallMsg{ + To: &contractAddr, + From: owner, + Data: transferData, + }) + s.Require().NoError(err) + + gasPrice := s.getGasPrice() + ercTransferTx := evmtypes.NewTx( + chainID, + nonce, + &contractAddr, + nil, + gas, + gasPrice, + nil, nil, + transferData, + nil, + ) + + ercTransferTx.From = owner.Hex() + err = ercTransferTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring) + s.Require().NoError(err) + err = s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, ercTransferTx.AsTransaction()) + s.Require().NoError(err) + + s.waitForTransaction() + + s.expectSuccessReceipt(ercTransferTx.AsTransaction().Hash()) + return ercTransferTx.AsTransaction().Hash() +} + +// waits 2 blocks time to keep tests stable +func (s *IntegrationTestSuite) waitForTransaction() { + err := s.network.WaitForNextBlock() + err = s.network.WaitForNextBlock() + s.Require().NoError(err) +} + func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) } diff --git a/tests/rpc/rpc_test.go b/tests/rpc/rpc_test.go index 97889cccf2..8ff18b6a06 100644 --- a/tests/rpc/rpc_test.go +++ b/tests/rpc/rpc_test.go @@ -8,10 +8,8 @@ package rpc import ( "bytes" - "encoding/hex" "encoding/json" "fmt" - "math/big" "net/http" "os" "testing" @@ -26,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" ) const ( @@ -35,9 +32,8 @@ const ( ) var ( - MODE = os.Getenv("MODE") - zeroString = "0x0" - from = []byte{} + MODE = os.Getenv("MODE") + from = []byte{} ) func TestMain(m *testing.M) { @@ -139,155 +135,6 @@ func callWithError(method string, params interface{}) (*Response, error) { return rpcRes, nil } -// turns a 0x prefixed hex string to a big.Int -func hexToBigInt(t *testing.T, in string) *big.Int { - s := in[2:] - b, err := hex.DecodeString(s) - require.NoError(t, err) - return big.NewInt(0).SetBytes(b) -} - -func TestBlockBloom(t *testing.T) { - hash := deployTestContractWithFunction(t) - receipt := waitForReceipt(t, hash) - - number := receipt["blockNumber"].(string) - param := []interface{}{number, false} - rpcRes := call(t, "eth_getBlockByNumber", param) - - block := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - - lb := hexToBigInt(t, block["logsBloom"].(string)) - require.NotEqual(t, big.NewInt(0), lb) - require.Equal(t, hash.String(), block["transactions"].([]interface{})[0]) -} - -func TestEth_GetLogs_NoLogs(t *testing.T) { - param := make([]map[string][]string, 1) - param[0] = make(map[string][]string) - param[0]["topics"] = []string{} - call(t, "eth_getLogs", param) -} - -func TestEth_GetLogs_Topics_AB(t *testing.T) { - // TODO: this test passes on when run on its own, but fails when run with the other tests - if testing.Short() { - t.Skip("skipping TestEth_GetLogs_Topics_AB") - } - - rpcRes := call(t, "eth_blockNumber", []string{}) - - var res hexutil.Uint64 - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - param := make([]map[string]interface{}, 1) - param[0] = make(map[string]interface{}) - param[0]["topics"] = []string{helloTopic, worldTopic} - param[0]["fromBlock"] = res.String() - - hash := deployTestContractWithFunction(t) - waitForReceipt(t, hash) - - rpcRes = call(t, "eth_getLogs", param) - - var logs []*ethtypes.Log - err = json.Unmarshal(rpcRes.Result, &logs) - require.NoError(t, err) - - require.Equal(t, 1, len(logs)) -} - -func TestEth_GetTransactionCount(t *testing.T) { - // TODO: this test passes on when run on its own, but fails when run with the other tests - if testing.Short() { - t.Skip("skipping TestEth_GetTransactionCount") - } - - prev := getNonce(t) - sendTestTransaction(t) - post := getNonce(t) - require.Equal(t, prev, post-1) -} - -func TestETH_GetBlockTransactionCountByHash(t *testing.T) { - txHash := sendTestTransaction(t) - - receipt := waitForReceipt(t, txHash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - - blockHash := receipt["blockHash"].(string) - param := []string{blockHash} - rpcRes := call(t, "eth_getBlockTransactionCountByHash", param) - - var res hexutil.Uint - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - require.Equal(t, "0x1", res.String()) -} - -func TestETH_GetBlockTransactionCountByHash_BlockHashNotFound(t *testing.T) { - anyBlockHash := "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" - param := []string{anyBlockHash} - rpcRes := call(t, "eth_getBlockTransactionCountByHash", param) - - var result interface{} - err := json.Unmarshal(rpcRes.Result, &result) - require.NoError(t, err) - require.Nil(t, result) -} - -func TestETH_GetTransactionByBlockHashAndIndex(t *testing.T) { - txHash := sendTestTransaction(t) - - receipt := waitForReceipt(t, txHash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - blockHash := receipt["blockHash"].(string) - - param := []string{blockHash, "0x0"} - rpcRes := call(t, "eth_getTransactionByBlockHashAndIndex", param) - - tx := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &tx) - require.NoError(t, err) - require.NotNil(t, tx) - require.Equal(t, blockHash, tx["blockHash"].(string)) - require.Equal(t, "0x0", tx["transactionIndex"].(string)) -} - -func TestETH_GetTransactionByBlockHashAndIndex_BlockHashNotFound(t *testing.T) { - anyBlockHash := "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" - - param := []string{anyBlockHash, "0x0"} - rpcRes := call(t, "eth_getTransactionByBlockHashAndIndex", param) - - var result interface{} - err := json.Unmarshal(rpcRes.Result, &result) - require.NoError(t, err) - require.Nil(t, result) -} - -func TestEth_GetTransactionLogs(t *testing.T) { - // TODO: this test passes on when run on its own, but fails when run with the other tests - if testing.Short() { - t.Skip("skipping TestEth_GetTransactionLogs") - } - - hash, _ := deployTestContract(t) - - param := []string{hash.String()} - rpcRes := call(t, "eth_getTransactionLogs", param) - - logs := new([]*ethtypes.Log) - err := json.Unmarshal(rpcRes.Result, logs) - require.NoError(t, err) - require.Equal(t, 1, len(*logs)) -} - func TestEth_protocolVersion(t *testing.T) { expectedRes := hexutil.Uint(ethermint.ProtocolVersion) @@ -301,25 +148,6 @@ func TestEth_protocolVersion(t *testing.T) { require.Equal(t, expectedRes, res, "expected: %s got: %s\n", expectedRes.String(), rpcRes.Result) } -func TestEth_chainId(t *testing.T) { - rpcRes := call(t, "eth_chainId", []string{}) - - var res hexutil.Uint - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - require.NotEqual(t, "0x0", res.String()) -} - -func TestEth_blockNumber(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) - - var res hexutil.Uint64 - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - t.Logf("Got block number: %s\n", res.String()) -} - func TestEth_coinbase(t *testing.T) { zeroAddress := hexutil.Bytes(common.Address{}.Bytes()) rpcRes := call(t, "eth_coinbase", []string{}) @@ -332,34 +160,6 @@ func TestEth_coinbase(t *testing.T) { require.NotEqual(t, zeroAddress.String(), res.String(), "expected: not %s got: %s\n", zeroAddress.String(), res.String()) } -func TestEth_GetBalance(t *testing.T) { - rpcRes := call(t, "eth_getBalance", []string{addrA, zeroString}) - - var res hexutil.Big - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - t.Logf("Got balance %s for %s\n", res.String(), addrA) - - // 0 if x == y; where x is res, y is 0 - if res.ToInt().Cmp(big.NewInt(0)) != 0 { - t.Errorf("expected balance: %d, got: %s", 0, res.String()) - } -} - -func TestEth_GetStorageAt(t *testing.T) { - expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - rpcRes := call(t, "eth_getStorageAt", []string{addrA, fmt.Sprint(addrAStoreKey), zeroString}) - - var storage hexutil.Bytes - err := storage.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - t.Logf("Got value [%X] for %s with key %X\n", storage, addrA, addrAStoreKey) - - require.True(t, bytes.Equal(storage, expectedRes), "expected: %d (%d bytes) got: %d (%d bytes)", expectedRes, len(expectedRes), storage, len(storage)) -} - func TestEth_GetProof(t *testing.T) { rpcRes := call(t, "eth_sendTransaction", makeEthTxParam()) @@ -387,49 +187,6 @@ func TestEth_GetProof(t *testing.T) { t.Logf("Got AccountResult %s", rpcRes.Result) } -func TestEth_GetCode(t *testing.T) { - expectedRes := hexutil.Bytes{} - rpcRes := call(t, "eth_getCode", []string{addrA, zeroString}) - - var code hexutil.Bytes - err := code.UnmarshalJSON(rpcRes.Result) - - require.NoError(t, err) - - t.Logf("Got code [%X] for %s\n", code, addrA) - require.True(t, bytes.Equal(expectedRes, code), "expected: %X got: %X", expectedRes, code) -} - -func TestEth_SendTransaction_Transfer(t *testing.T) { - rpcRes := call(t, "eth_sendTransaction", makeEthTxParam()) - - var hash hexutil.Bytes - err := json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - - receipt := waitForReceipt(t, hash) - require.NotNil(t, receipt) - require.Equal(t, "0x1", receipt["status"].(string)) -} - -func TestEth_SendTransaction_ContractDeploy(t *testing.T) { - param := makeTestContractDeployParam(t, true) - rpcRes, err := callWithError("eth_sendTransaction", param) - require.NoError(t, err) - - var hash hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) -} - -func TestEth_SendTransaction_ContractDeploy_no_gas_param(t *testing.T) { - t.Skip("Moved to tests/e2e/integration_test.go#TestSendTransactionContractDeploymentNoGas") - param := makeTestContractDeployParam(t, false) - _, err := callWithError("eth_sendTransaction", param) - // server returns internal error. - require.Error(t, err) -} - func TestEth_NewFilter(t *testing.T) { param := make([]map[string][]string, 1) param[0] = make(map[string][]string) @@ -521,97 +278,6 @@ func sendTestTransaction(t *testing.T) hexutil.Bytes { return hash } -func TestEth_GetTransactionReceipt(t *testing.T) { - hash := sendTestTransaction(t) - - receipt := waitForReceipt(t, hash) - - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - require.Equal(t, []interface{}{}, receipt["logs"].([]interface{})) -} - -// deployTestERC20Contract deploys a contract that emits an event in the constructor -func deployTestERC20Contract(t *testing.T) common.Address { - gasPrice := GetGasPrice(t) - - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - - ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", common.BytesToAddress(from), big.NewInt(100000000)) - require.NoError(t, err) - data := append(evmtypes.ERC20Contract.Bin, ctorArgs...) - param[0]["data"] = hexutil.Encode(data) - - param[0]["gas"] = "0x200000" - param[0]["gasPrice"] = gasPrice - - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - - receipt := expectSuccessReceipt(t, hash) - contractAddress := common.HexToAddress(receipt["contractAddress"].(string)) - require.NotEqual(t, common.Address{}, contractAddress) - - require.NotNil(t, receipt["logs"]) - - return contractAddress -} - -// sendTestERC20Transaction sends a typical erc20 transfer transaction -func sendTestERC20Transaction(t *testing.T, contract common.Address, amount *big.Int) hexutil.Bytes { - // transfer - gasPrice := GetGasPrice(t) - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["to"] = contract.Hex() - data, err := evmtypes.ERC20Contract.ABI.Pack("transfer", common.BigToAddress(big.NewInt(1)), amount) - require.NoError(t, err) - param[0]["data"] = hexutil.Encode(data) - param[0]["gas"] = "0x50000" - param[0]["gasPrice"] = gasPrice - - rpcRes := call(t, "eth_sendTransaction", param) - - var hash hexutil.Bytes - err = json.Unmarshal(rpcRes.Result, &hash) - require.NoError(t, err) - return hash -} - -func TestEth_GetTransactionReceipt_ERC20Transfer(t *testing.T) { - // deploy erc20 contract - contract := deployTestERC20Contract(t) - amount := big.NewInt(10) - hash := sendTestERC20Transaction(t, contract, amount) - receipt := expectSuccessReceipt(t, hash) - - require.Equal(t, 1, len(receipt["logs"].([]interface{}))) - log := receipt["logs"].([]interface{})[0].(map[string]interface{}) - - require.Equal(t, contract, common.HexToAddress(log["address"].(string))) - - valueBz, err := hexutil.Decode(log["data"].(string)) - require.NoError(t, err) - require.Equal(t, amount, big.NewInt(0).SetBytes(valueBz)) - - require.Equal(t, false, log["removed"].(bool)) - require.Equal(t, "0x0", log["logIndex"].(string)) - require.Equal(t, "0x0", log["transactionIndex"].(string)) - - expectedTopics := []interface{}{ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000" + fmt.Sprintf("%x", from), - "0x0000000000000000000000000000000000000000000000000000000000000001", - } - require.Equal(t, expectedTopics, log["topics"].([]interface{})) -} - // deployTestContract deploys a contract that emits an event in the constructor func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) { gasPrice := GetGasPrice(t) @@ -635,24 +301,6 @@ func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) { return hash, receipt } -func TestEth_GetTransactionReceipt_ContractDeployment(t *testing.T) { - nonce := getNonce(t) - _, receipt := deployTestContract(t) - - addrBz, err := hexutil.Decode(receipt["contractAddress"].(string)) - require.NoError(t, err) - - addr := common.BytesToAddress(addrBz) - require.Equal(t, crypto.CreateAddress(common.BytesToAddress(from), uint64(nonce)), addr) - require.Greater(t, len(receipt["logs"].([]interface{})), 0) - - rpcRes := call(t, "eth_getCode", []string{addr.Hex(), "latest"}) - var code hexutil.Bytes - err = code.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - require.NotEmpty(t, code) -} - func getTransactionReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { param := []string{hash.String()} rpcRes := call(t, "eth_getTransactionReceipt", param) @@ -681,13 +329,6 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { } } -func expectSuccessReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} { - receipt := waitForReceipt(t, hash) - require.NotNil(t, receipt, "transaction failed") - require.Equal(t, "0x1", receipt["status"].(string)) - return receipt -} - func TestEth_GetFilterChanges_NoTopics(t *testing.T) { rpcRes := call(t, "eth_blockNumber", []string{}) @@ -719,16 +360,6 @@ func TestEth_GetFilterChanges_NoTopics(t *testing.T) { require.Equal(t, 1, len(logs)) } -func TestEth_GetFilterChanges_Addresses(t *testing.T) { - t.Skip() - // TODO: need transaction receipts to determine contract deployment address -} - -func TestEth_GetFilterChanges_BlockHash(t *testing.T) { - t.Skip() - // TODO: need transaction receipts to determine tx block -} - // hash of Hello event var helloTopic = "0x775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd738898" @@ -838,11 +469,6 @@ func TestEth_GetFilterChanges_Topics_XB(t *testing.T) { require.Equal(t, 1, len(logs)) } -func TestEth_GetFilterChanges_Topics_XXC(t *testing.T) { - t.Skip() - // TODO: call test function, need tx receipts to determine contract address -} - func TestEth_PendingTransactionFilter(t *testing.T) { rpcRes := call(t, "eth_newPendingTransactionFilter", []string{}) @@ -865,52 +491,6 @@ func TestEth_PendingTransactionFilter(t *testing.T) { require.True(t, len(txs) >= 2, "could not get any txs", "changesRes.Result", string(changesRes.Result)) } -func getNonce(t *testing.T) hexutil.Uint64 { - param := []interface{}{hexutil.Bytes(from), "latest"} - rpcRes := call(t, "eth_getTransactionCount", param) - - var nonce hexutil.Uint64 - err := json.Unmarshal(rpcRes.Result, &nonce) - require.NoError(t, err) - return nonce -} - -func TestEth_EstimateGas(t *testing.T) { - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["to"] = "0x1122334455667788990011223344556677889900" - param[0]["value"] = "0x1" - param[0]["gas"] = "0x5209" - rpcRes := call(t, "eth_estimateGas", param) - require.NotNil(t, rpcRes) - - var gas string - err := json.Unmarshal(rpcRes.Result, &gas) - require.NoError(t, err, string(rpcRes.Result)) - require.Equal(t, "0x5208", gas) -} - -func TestEth_EstimateGas_ContractDeployment(t *testing.T) { - t.Skip("Moved to tests/e2e/integration_test.go#TestEstimateGasContractDeployment") - bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" - - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["data"] = bytecode - - rpcRes := call(t, "eth_estimateGas", param) - require.NotNil(t, rpcRes) - require.NotEmpty(t, rpcRes.Result) - - var gas hexutil.Uint64 - err := json.Unmarshal(rpcRes.Result, &gas) - require.NoError(t, err, string(rpcRes.Result)) - - require.Equal(t, "0x1879c", gas.String()) -} - func TestEth_ExportAccount_WithStorage(t *testing.T) { t.Skip("skipping TestEth_ExportAccount_WithStorage due to the server haven't implmented yet") @@ -957,78 +537,6 @@ func TestEth_ExportAccount_WithStorage(t *testing.T) { require.NotEqual(t, evmtypes.Storage(nil), account.Storage) } -func TestEth_GetBlockByHash(t *testing.T) { - param := []interface{}{"0x1", false} - rpcRes := call(t, "eth_getBlockByNumber", param) - - block := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - blockHash := block["hash"].(string) - - param = []interface{}{blockHash, false} - rpcRes = call(t, "eth_getBlockByHash", param) - block = make(map[string]interface{}) - err = json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - require.Equal(t, "0x1", block["number"].(string)) -} - -func TestEth_GetBlockByHash_BlockHashNotFound(t *testing.T) { - anyBlockHash := "0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35" - param := []interface{}{anyBlockHash, false} - rpcRes := call(t, "eth_getBlockByHash", param) - - var result interface{} - err := json.Unmarshal(rpcRes.Result, &result) - require.NoError(t, err) - require.Nil(t, result) -} - -func TestEth_GetBlockByNumber(t *testing.T) { - param := []interface{}{"0x1", false} - rpcRes := call(t, "eth_getBlockByNumber", param) - - block := make(map[string]interface{}) - err := json.Unmarshal(rpcRes.Result, &block) - require.NoError(t, err) - require.Equal(t, "0x", block["extraData"].(string)) - require.Equal(t, []interface{}{}, block["uncles"].([]interface{})) -} - -func TestEth_GetLogs(t *testing.T) { - rpcRes := call(t, "eth_blockNumber", []string{}) - - var res hexutil.Uint64 - err := res.UnmarshalJSON(rpcRes.Result) - require.NoError(t, err) - - param := make([]map[string]interface{}, 1) - param[0] = make(map[string]interface{}) - param[0]["topics"] = []string{helloTopic, worldTopic} - param[0]["fromBlock"] = res.String() - - deployTestContractWithFunction(t) - - // get filter changes - logRes := call(t, "eth_getLogs", param) - - var logs []*ethtypes.Log - err = json.Unmarshal(logRes.Result, &logs) - require.NoError(t, err) - - require.Equal(t, 1, len(logs)) - - // filter log with address - param[0] = make(map[string]interface{}) - param[0]["address"] = "0x" + fmt.Sprintf("%x", from) - param[0]["fromBlock"] = res.String() - err = json.Unmarshal(logRes.Result, &logs) - require.NoError(t, err) - - require.Equal(t, 1, len(logs)) -} - func makeEthTxParam() []map[string]string { param := make([]map[string]string, 1) param[0] = make(map[string]string) @@ -1041,20 +549,6 @@ func makeEthTxParam() []map[string]string { return param } -func makeTestContractDeployParam(t *testing.T, withGas bool) []map[string]string { - param := make([]map[string]string, 1) - param[0] = make(map[string]string) - param[0]["from"] = "0x" + fmt.Sprintf("%x", from) - param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" - if withGas { - gasPrice := GetGasPrice(t) - param[0]["gas"] = "0x200000" - param[0]["gasPrice"] = gasPrice - } - - return param -} - func TestEth_EthResend(t *testing.T) { tx := make(map[string]string) tx["from"] = "0x" + fmt.Sprintf("%x", from) diff --git a/x/evm/types/SimpleStorageContract.json b/x/evm/types/SimpleStorageContract.json new file mode 100644 index 0000000000..4d8bb0afe8 --- /dev/null +++ b/x/evm/types/SimpleStorageContract.json @@ -0,0 +1,4 @@ +{ + "abi": "[\n\t{\n\t\t\"inputs\": [],\n\t\t\"name\": \"get\",\n\t\t\"outputs\": [\n\t\t\t{\n\t\t\t\t\"internalType\": \"uint160\",\n\t\t\t\t\"name\": \"\",\n\t\t\t\t\"type\": \"uint160\"\n\t\t\t}\n\t\t],\n\t\t\"stateMutability\": \"view\",\n\t\t\"type\": \"function\"\n\t},\n\t{\n\t\t\"inputs\": [\n\t\t\t{\n\t\t\t\t\"internalType\": \"uint160\",\n\t\t\t\t\"name\": \"input\",\n\t\t\t\t\"type\": \"uint160\"\n\t\t\t}\n\t\t],\n\t\t\"name\": \"store\",\n\t\t\"outputs\": [],\n\t\t\"stateMutability\": \"nonpayable\",\n\t\t\"type\": \"function\"\n\t}\n]", + "bin": "608060405234801561001057600080fd5b506101bf806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636d4ce63c1461003b578063d04ad49514610059575b600080fd5b610043610075565b6040516100509190610132565b60405180910390f35b610073600480360381019061006e91906100f6565b61009e565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000813590506100f081610172565b92915050565b60006020828403121561010c5761010b61016d565b5b600061011a848285016100e1565b91505092915050565b61012c8161014d565b82525050565b60006020820190506101476000830184610123565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600080fd5b61017b8161014d565b811461018657600080fd5b5056fea26469706673582212204c98c8f28598d29acc328cb34578de54cbed70b20bf9364897d48b2381f0c78b64736f6c63430008070033" +} diff --git a/x/evm/types/compiled_contract.go b/x/evm/types/compiled_contract.go index e36704fc42..081d815bed 100644 --- a/x/evm/types/compiled_contract.go +++ b/x/evm/types/compiled_contract.go @@ -74,6 +74,12 @@ var ( // ERC20Contract is the compiled test erc20 contract ERC20Contract CompiledContract + //go:embed SimpleStorageContract.json + simpleStorageJSON []byte + + // SimpleStorageContract is the compiled test simple storage contract + SimpleStorageContract CompiledContract + //go:embed TestMessageCall.json testMessageCallJSON []byte @@ -99,4 +105,13 @@ func init() { if len(TestMessageCall.Bin) == 0 { panic("load contract failed") } + + err = json.Unmarshal(simpleStorageJSON, &SimpleStorageContract) + if err != nil { + panic(err) + } + + if len(TestMessageCall.Bin) == 0 { + panic("load contract failed") + } } From fd3c803b44b23128d3f54c39cc5ac3c2dca9c3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= <31522760+fedekunze@users.noreply.github.com> Date: Mon, 24 Jan 2022 15:35:02 +0100 Subject: [PATCH 10/12] ci: semgrep config (#917) * ci: enable semgrep config * fix config * ignore grpc web --- .github/workflows/semgrep.yml | 41 +++++++++++++++++++++++++++++++++++ .semgrepignore | 29 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 .github/workflows/semgrep.yml create mode 100644 .semgrepignore diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000000..9570d52ddc --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,41 @@ +name: Semgrep +on: + # Scan changed files in PRs, block on new issues only (existing issues ignored) + pull_request: {} + push: + branches: + - main + paths: + - .github/workflows/semgrep.yml + schedule: + - cron: '0 0 * * 0' +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + if: (github.actor != 'dependabot[bot]') + steps: + - uses: actions/checkout@v2 + - name: Get Diff + uses: technote-space/get-diff-action@v6.0.1 + with: + PATTERNS: | + **/*.go + **/*.js + **/*.ts + **/*.sol + go.mod + go.sum + - uses: returntocorp/semgrep-action@v1 + with: + publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} + # Upload findings to GitHub Advanced Security Dashboard [step 1/2] + # See also the next step. + generateSarif: "1" + if: "env.GIT_DIFF_FILTERED != ''" + # Upload findings to GitHub Advanced Security Dashboard [step 2/2] + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: semgrep.sarif + if: "env.GIT_DIFF_FILTERED != ''" diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 0000000000..cb655af10c --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,29 @@ +# Ignore git items +.gitignore +.git/ +:include .gitignore + +# Common large paths +node_modules/ +build/ +dist/ +vendor/ +.env/ +.venv/ +.tox/ +*.min.js +*.pb.gw.go + +# Common test paths +test/ +tests/ +*_test.go + +# Semgrep rules folder +.semgrep + +# Semgrep-action log folder +.semgrep_logs/ + +# Documentation +client/docs/ \ No newline at end of file From e39a74998ebde14f684b7e51505df862d90a5335 Mon Sep 17 00:00:00 2001 From: yihuang Date: Wed, 26 Jan 2022 18:36:07 +0800 Subject: [PATCH 11/12] fix: default base fee state in genesis (#919) * fix defualt base fee state in genesis Closes: #918 Solution: - initialise the default base fee value in genesis * changelog --- CHANGELOG.md | 1 + x/evm/keeper/keeper_test.go | 7 +++++-- x/evm/keeper/state_transition_test.go | 2 +- x/feemarket/keeper/grpc_query_test.go | 6 ++++-- x/feemarket/types/genesis.go | 6 ++++-- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a99751bf4a..2d6f8e13cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (app) [tharsis#873](https://github.com/tharsis/ethermint/pull/873) Validate code hash in GenesisAccount * (evm) [tharsis#901](https://github.com/tharsis/ethermint/pull/901) Support multiple MsgEthereumTx in single tx. * (config) [tharsis#908](https://github.com/tharsis/ethermint/pull/908) Add api.enable flag for Cosmos SDK Rest server +* (feemarket) [tharsis#919](https://github.com/tharsis/ethermint/pull/919) Initialize baseFee in default genesis state. ### Bug Fixes diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 4973863a66..e4464d47af 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -87,13 +87,16 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) { suite.consAddress = sdk.ConsAddress(priv.PubKey().Address()) suite.app = app.Setup(checkTx, func(app *app.EthermintApp, genesis simapp.GenesisState) simapp.GenesisState { + feemarketGenesis := feemarkettypes.DefaultGenesisState() if suite.enableFeemarket { - feemarketGenesis := feemarkettypes.DefaultGenesisState() feemarketGenesis.Params.EnableHeight = 1 feemarketGenesis.Params.NoBaseFee = false feemarketGenesis.BaseFee = sdk.NewInt(feemarketGenesis.Params.InitialBaseFee) - genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) + } else { + feemarketGenesis.Params.NoBaseFee = true + feemarketGenesis.BaseFee = sdk.NewInt(0) } + genesis[feemarkettypes.ModuleName] = app.AppCodec().MustMarshalJSON(feemarketGenesis) if !suite.enableLondonHF { evmGenesis := types.DefaultGenesisState() maxInt := sdk.NewInt(math.MaxInt64) diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index c2586ba494..37314ccbb5 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -501,7 +501,7 @@ func (suite *KeeperTestSuite) TestEVMConfig() { suite.Require().NoError(err) suite.Require().Equal(types.DefaultParams(), cfg.Params) // london hardfork is enabled by default - suite.Require().Equal(new(big.Int), cfg.BaseFee) + suite.Require().Equal(big.NewInt(0), cfg.BaseFee) suite.Require().Equal(suite.address, cfg.CoinBase) suite.Require().Equal(types.DefaultParams().ChainConfig.EthereumConfig(big.NewInt(9000)), cfg.ChainConfig) } diff --git a/x/feemarket/keeper/grpc_query_test.go b/x/feemarket/keeper/grpc_query_test.go index 8bda0f2442..b97f4d311a 100644 --- a/x/feemarket/keeper/grpc_query_test.go +++ b/x/feemarket/keeper/grpc_query_test.go @@ -2,6 +2,7 @@ package keeper_test import ( sdk "github.com/cosmos/cosmos-sdk/types" + ethparams "github.com/ethereum/go-ethereum/params" "github.com/tharsis/ethermint/x/feemarket/types" ) @@ -41,9 +42,10 @@ func (suite *KeeperTestSuite) TestQueryBaseFee() { expPass bool }{ { - "pass - nil Base Fee", + "pass - default Base Fee", func() { - expRes = &types.QueryBaseFeeResponse{} + initialBaseFee := sdk.NewInt(ethparams.InitialBaseFee) + expRes = &types.QueryBaseFeeResponse{BaseFee: &initialBaseFee} }, true, }, diff --git a/x/feemarket/types/genesis.go b/x/feemarket/types/genesis.go index a81829e98e..b92f5b92ad 100644 --- a/x/feemarket/types/genesis.go +++ b/x/feemarket/types/genesis.go @@ -4,13 +4,15 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/params" ) // DefaultGenesisState sets default fee market genesis state. func DefaultGenesisState() *GenesisState { return &GenesisState{ - Params: DefaultParams(), - BaseFee: sdk.ZeroInt(), + Params: DefaultParams(), + // the default base fee should be initialized because the default enable height is zero. + BaseFee: sdk.NewIntFromUint64(params.InitialBaseFee), BlockGas: 0, } } From 724a06632b54660ce73d528ec5b8eb51a4bc56ed Mon Sep 17 00:00:00 2001 From: yihuang Date: Wed, 26 Jan 2022 18:44:41 +0800 Subject: [PATCH 12/12] fix: minimal-gas-prices and baseFeePerGas conflicts (#916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Problem: minimal-gas-prices and baseFeePerGas conflicts Closes: #915 Solution: - Don't check min-gas-price for evm tx if london hardfork and feemarket enabled. comments and cleanup changelog * fix zero fee coins Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- CHANGELOG.md | 1 + app/ante/eth.go | 57 +++++++++++++----------------- app/ante/eth_test.go | 2 +- app/ante/handler_options.go | 6 ++-- rpc/ethereum/backend/backend.go | 34 ++++-------------- rpc/ethereum/namespaces/eth/api.go | 20 +++++++---- x/evm/keeper/utils.go | 5 +++ x/evm/types/msg.go | 9 +++-- 8 files changed, 57 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d6f8e13cb..598ea49616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (ante) [\#866](https://github.com/tharsis/ethermint/pull/866) `NewAnteHandler` constructor now receives a `HandlerOptions` field. * (evm) [\#849](https://github.com/tharsis/ethermint/pull/849) `PostTxProcessing` hook now takes an Ethereum tx `Receipt` and a `from` `Address` as arguments. +* (ante) [#916](https://github.com/tharsis/ethermint/pull/916) don't check min-gas-price for eth tx if london hardfork enabled and feemarket enabled. ### State Machine Breaking diff --git a/app/ante/eth.go b/app/ante/eth.go index ae4dce5a57..4ed65a644b 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -221,15 +221,13 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block // context rules. type CanTransferDecorator struct { - evmKeeper EVMKeeper - feemarketKeeper evmtypes.FeeMarketKeeper + evmKeeper EVMKeeper } // NewCanTransferDecorator creates a new CanTransferDecorator instance. -func NewCanTransferDecorator(evmKeeper EVMKeeper, fmk evmtypes.FeeMarketKeeper) CanTransferDecorator { +func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { return CanTransferDecorator{ - evmKeeper: evmKeeper, - feemarketKeeper: fmk, + evmKeeper: evmKeeper, } } @@ -477,45 +475,38 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul // If fee is high enough or not CheckTx, then call next AnteHandler // CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator type EthMempoolFeeDecorator struct { - feemarketKeeper evmtypes.FeeMarketKeeper - evmKeeper EVMKeeper + evmKeeper EVMKeeper } -func NewEthMempoolFeeDecorator(ek EVMKeeper, fmk evmtypes.FeeMarketKeeper) EthMempoolFeeDecorator { +func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator { return EthMempoolFeeDecorator{ - feemarketKeeper: fmk, - evmKeeper: ek, + evmKeeper: ek, } } // AnteHandle ensures that the provided fees meet a minimum threshold for the validator, // if this is a CheckTx. This is only for local mempool purposes, and thus // is only ran on check tx. +// It only do the check if london hardfork not enabled or feemarket not enabled, because in that case feemarket will take over the task. func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { if ctx.IsCheckTx() && !simulate { - for _, msg := range tx.GetMsgs() { - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) - } - - var feeAmt *big.Int - - params := mfd.evmKeeper.GetParams(ctx) - chainID := mfd.evmKeeper.ChainID() - ethCfg := params.ChainConfig.EthereumConfig(chainID) - evmDenom := params.EvmDenom - baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg) - if baseFee != nil { - feeAmt = ethMsg.GetEffectiveFee(baseFee) - } else { - feeAmt = ethMsg.GetFee() - } - - glDec := sdk.NewDec(int64(ethMsg.GetGas())) - requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) - if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) + params := mfd.evmKeeper.GetParams(ctx) + ethCfg := params.ChainConfig.EthereumConfig(mfd.evmKeeper.ChainID()) + baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg) + if baseFee == nil { + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + + evmDenom := params.EvmDenom + feeAmt := ethMsg.GetFee() + glDec := sdk.NewDec(int64(ethMsg.GetGas())) + requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) + if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) + } } } } diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index e181e1fc96..80f5455baa 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -293,7 +293,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() { } func (suite AnteTestSuite) TestCanTransferDecorator() { - dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper, suite.app.FeeMarketKeeper) + dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper) addr, privKey := tests.NewAddrKey() diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index b960df3947..1defef7bc5 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -48,13 +48,13 @@ func (options HandlerOptions) Validate() error { func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { return sdk.ChainAnteDecorators( - NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first - NewEthMempoolFeeDecorator(options.EvmKeeper, options.FeeMarketKeeper), // Check eth effective gas price against minimal-gas-prices + NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first + NewEthMempoolFeeDecorator(options.EvmKeeper), // Check eth effective gas price against minimal-gas-prices NewEthValidateBasicDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper), NewEthGasConsumeDecorator(options.EvmKeeper), - NewCanTransferDecorator(options.EvmKeeper, options.FeeMarketKeeper), + NewCanTransferDecorator(options.EvmKeeper), NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator. ) } diff --git a/rpc/ethereum/backend/backend.go b/rpc/ethereum/backend/backend.go index 0cb68c0560..850737dcb5 100644 --- a/rpc/ethereum/backend/backend.go +++ b/rpc/ethereum/backend/backend.go @@ -10,10 +10,8 @@ import ( "time" "github.com/cosmos/cosmos-sdk/client/flags" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/server" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -802,23 +800,6 @@ func (e *EVMBackend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash return common.Hash{}, err } - // Assemble transaction from fields - builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) - if !ok { - e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error()) - } - - option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) - if err != nil { - e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error()) - return common.Hash{}, err - } - - builder.SetExtensionOptions(option) - if err = builder.SetMsgs(msg); err != nil { - e.logger.Error("builder.SetMsgs failed", "error", err.Error()) - } - // Query params to use the EVM denomination res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { @@ -826,19 +807,16 @@ func (e *EVMBackend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash return common.Hash{}, err } - txData, err := evmtypes.UnpackTxData(msg.Data) + // Assemble transaction from fields + tx, err := msg.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom) if err != nil { - e.logger.Error("failed to unpack tx data", "error", err.Error()) + e.logger.Error("build cosmos tx failed", "error", err.Error()) return common.Hash{}, err } - fees := sdk.Coins{sdk.NewCoin(res.Params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))} - builder.SetFeeAmount(fees) - builder.SetGasLimit(msg.GetGas()) - // Encode transaction by default Tx encoder txEncoder := e.clientCtx.TxConfig.TxEncoder() - txBytes, err := txEncoder(builder.GetTx()) + txBytes, err := txEncoder(tx) if err != nil { e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) return common.Hash{}, err @@ -976,9 +954,9 @@ func (e *EVMBackend) ChainConfig() *params.ChainConfig { } // SuggestGasTipCap returns the suggested tip cap +// always return zero since we don't support tx prioritization yet. func (e *EVMBackend) SuggestGasTipCap() (*big.Int, error) { - out := new(big.Int).SetInt64(e.RPCMinGasPrice()) - return out, nil + return big.NewInt(0), nil } // BaseFee returns the base fee tracked by the Fee Market module. If the base fee is not enabled, diff --git a/rpc/ethereum/namespaces/eth/api.go b/rpc/ethereum/namespaces/eth/api.go index 2cac0699d8..a9f70e8690 100644 --- a/rpc/ethereum/namespaces/eth/api.go +++ b/rpc/ethereum/namespaces/eth/api.go @@ -193,16 +193,22 @@ func (e *PublicAPI) Hashrate() hexutil.Uint64 { // GasPrice returns the current gas price based on Ethermint's gas price oracle. func (e *PublicAPI) GasPrice() (*hexutil.Big, error) { e.logger.Debug("eth_gasPrice") - tipcap, err := e.backend.SuggestGasTipCap() - if err != nil { - return nil, err - } - + var ( + result *big.Int + err error + ) if head := e.backend.CurrentHeader(); head.BaseFee != nil { - tipcap.Add(tipcap, head.BaseFee) + result, err = e.backend.SuggestGasTipCap() + if err != nil { + return nil, err + } + + result = result.Add(result, head.BaseFee) + } else { + result = big.NewInt(e.backend.RPCMinGasPrice()) } - return (*hexutil.Big)(tipcap), nil + return (*hexutil.Big)(result), nil } // MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions. diff --git a/x/evm/keeper/utils.go b/x/evm/keeper/utils.go index 3846e03afe..ed4a2e2f0d 100644 --- a/x/evm/keeper/utils.go +++ b/x/evm/keeper/utils.go @@ -66,6 +66,11 @@ func (k Keeper) DeductTxCostsFromUserBalance( feeAmt = txData.Fee() } + if feeAmt.Sign() == 0 { + // zero fee, no need to deduct + return sdk.NewCoins(), nil + } + fees := sdk.Coins{sdk.NewCoin(denom, sdk.NewIntFromBigInt(feeAmt))} // deduct the full gas cost from the user balance diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 469fcd8a9c..7cec50c944 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -330,11 +330,10 @@ func (msg *MsgEthereumTx) BuildTx(b client.TxBuilder, evmDenom string) (signing. if err != nil { return nil, err } - fees := sdk.Coins{ - { - Denom: evmDenom, - Amount: sdk.NewIntFromBigInt(txData.Fee()), - }, + fees := make(sdk.Coins, 0) + feeAmt := sdk.NewIntFromBigInt(txData.Fee()) + if feeAmt.Sign() > 0 { + fees = append(fees, sdk.NewCoin(evmDenom, feeAmt)) } builder.SetExtensionOptions(option)