From 5b2f068a03d15e3cc4834d5c009dc10c24409679 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Fri, 17 Apr 2020 18:32:01 -0400 Subject: [PATCH] ante: switch ethAnteHandler to use AnteDecorators (#252) * ante: switch ethAnteHandler to use AnteDecorators * ante: cleanup panic recover, update MsgEthereumTx GetSigners * ante: remove EthIntrinsicGasDecorator in favor of EthGasConsumeDecorator * migrate errors --- app/ante/ante.go | 261 ++---------------------------- app/ante/ante_test.go | 2 +- app/ante/doc.go | 11 ++ app/ante/eth.go | 361 ++++++++++++++++++++++++++++++++++++++++++ app/export.go | 6 + app/test_helpers.go | 4 +- x/evm/types/msg.go | 41 ++++- 7 files changed, 428 insertions(+), 258 deletions(-) create mode 100644 app/ante/doc.go create mode 100644 app/ante/eth.go diff --git a/app/ante/ante.go b/app/ante/ante.go index 2a407fb8..7a9003ba 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -1,9 +1,6 @@ package ante import ( - "fmt" - "math/big" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth" @@ -11,11 +8,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/ethermint/crypto" - emint "github.com/cosmos/ethermint/types" evmtypes "github.com/cosmos/ethermint/x/evm/types" - ethcore "github.com/ethereum/go-ethereum/core" - tmcrypto "github.com/tendermint/tendermint/crypto" ) @@ -34,10 +28,10 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle return func( ctx sdk.Context, tx sdk.Tx, sim bool, ) (newCtx sdk.Context, err error) { - - switch castTx := tx.(type) { + var anteHandler sdk.AnteHandler + switch tx.(type) { case auth.StdTx: - stdAnte := sdk.ChainAnteDecorators( + anteHandler = sdk.ChainAnteDecorators( authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first authante.NewMempoolFeeDecorator(), authante.NewValidateBasicDecorator(), @@ -51,14 +45,21 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator ) - return stdAnte(ctx, tx, sim) - case evmtypes.MsgEthereumTx: - return ethAnteHandler(ctx, ak, sk, &castTx, sim) - + anteHandler = sdk.ChainAnteDecorators( + NewEthSetupContextDecorator(), // outermost AnteDecorator. EthSetUpContext must be called first + NewEthMempoolFeeDecorator(), + NewEthSigVerificationDecorator(), + NewAccountVerificationDecorator(ak), + NewNonceVerificationDecorator(ak), + NewEthGasConsumeDecorator(ak, sk), + NewIncrementSenderSequenceDecorator(ak), // innermost AnteDecorator. + ) default: return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) } + + return anteHandler(ctx, tx, sim) } } @@ -78,237 +79,3 @@ func sigGasConsumer( return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey) } } - -// ---------------------------------------------------------------------------- -// Ethereum Ante Handler - -// ethAnteHandler defines an internal ante handler for an Ethereum transaction -// ethTxMsg. During CheckTx, the transaction is passed through a series of -// pre-message execution validation checks such as signature and account -// verification in addition to minimum fees being checked. Otherwise, during -// DeliverTx, the transaction is simply passed to the EVM which will also -// perform the same series of checks. The distinction is made in CheckTx to -// prevent spam and DoS attacks. -func ethAnteHandler( - ctx sdk.Context, ak auth.AccountKeeper, sk types.SupplyKeeper, - ethTxMsg *evmtypes.MsgEthereumTx, sim bool, -) (newCtx sdk.Context, err error) { - - var senderAddr sdk.AccAddress - - // This is done to ignore costs in Ante handler checks - ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) - - if ctx.IsCheckTx() { - // Only perform pre-message (Ethereum transaction) execution validation - // during CheckTx. Otherwise, during DeliverTx the EVM will handle them. - if senderAddr, err = validateEthTxCheckTx(ctx, ak, ethTxMsg); err != nil { - return ctx, err - } - } else { - // This is still currently needed to retrieve the sender address - if senderAddr, err = validateSignature(ctx, ethTxMsg); err != nil { - return ctx, err - } - - // Explicit nonce check is also needed in case of multiple txs with same nonce not being handled - if err := checkNonce(ctx, ak, ethTxMsg, senderAddr); err != nil { - return ctx, err - } - } - - // Recover and catch out of gas error - defer func() { - if r := recover(); r != nil { - switch rType := r.(type) { - case sdk.ErrorOutOfGas: - err = sdkerrors.Wrapf( - sdkerrors.ErrOutOfGas, - "out of gas in location: %v; gasUsed: %d", - rType.Descriptor, ctx.GasMeter().GasConsumed(), - ) - default: - panic(r) - } - } - }() - - // Fetch sender account from signature - senderAcc, err := auth.GetSignerAcc(ctx, ak, senderAddr) - if err != nil { - return ctx, err - } - - // Charge sender for gas up to limit - if ethTxMsg.Data.GasLimit != 0 { - // Cost calculates the fees paid to validators based on gas limit and price - cost := new(big.Int).Mul(ethTxMsg.Data.Price, new(big.Int).SetUint64(ethTxMsg.Data.GasLimit)) - - feeAmt := sdk.NewCoins( - sdk.NewCoin(emint.DenomDefault, sdk.NewIntFromBigInt(cost)), - ) - - err = auth.DeductFees(sk, ctx, senderAcc, feeAmt) - if err != nil { - return ctx, err - } - } - - // Set gas meter after ante handler to ignore gaskv costs - newCtx = auth.SetGasMeter(sim, ctx, ethTxMsg.Data.GasLimit) - - gas, _ := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, true) - newCtx.GasMeter().ConsumeGas(gas, "eth intrinsic gas") - - // Increment sequence of sender - acc := ak.GetAccount(ctx, senderAddr) - if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { - panic(err) - } - ak.SetAccount(ctx, acc) - - return newCtx, nil -} - -func validateEthTxCheckTx( - ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, -) (sdk.AccAddress, error) { - // Validate sufficient fees have been provided that meet a minimum threshold - // defined by the proposer (for mempool purposes during CheckTx). - if err := ensureSufficientMempoolFees(ctx, ethTxMsg); err != nil { - return nil, err - } - - // validate enough intrinsic gas - if err := validateIntrinsicGas(ethTxMsg); err != nil { - return nil, err - } - - signer, err := validateSignature(ctx, ethTxMsg) - if err != nil { - return nil, err - } - - // validate account (nonce and balance checks) - if err := validateAccount(ctx, ak, ethTxMsg, signer); err != nil { - return nil, err - } - - return sdk.AccAddress(signer.Bytes()), nil -} - -// Validates signature and returns sender address -func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.MsgEthereumTx) (sdk.AccAddress, error) { - // parse the chainID from a string to a base-10 integer - chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) - if !ok { - return nil, sdkerrors.Wrap(emint.ErrInvalidChainID, ctx.ChainID()) - } - - // validate sender/signature - signer, err := ethTxMsg.VerifySig(chainID) - if err != nil { - return nil, sdkerrors.Wrap(err, "signature verification failed") - } - - return sdk.AccAddress(signer.Bytes()), nil -} - -// validateIntrinsicGas validates that the Ethereum tx message has enough to -// cover intrinsic gas. Intrinsic gas for a transaction is the amount of gas -// that the transaction uses before the transaction is executed. The gas is a -// constant value of 21000 plus any cost inccured by additional bytes of data -// supplied with the transaction. -func validateIntrinsicGas(ethTxMsg *evmtypes.MsgEthereumTx) error { - gas, err := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, true) - if err != nil { - return sdkerrors.Wrap(err, "failed to compute intrinsic gas cost") - } - - if ethTxMsg.Data.GasLimit < gas { - return fmt.Errorf( - "intrinsic gas too low: %d < %d", ethTxMsg.Data.GasLimit, gas, - ) - } - - return nil -} - -// validateAccount validates the account nonce and that the account has enough -// funds to cover the tx cost. -func validateAccount( - ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, signer sdk.AccAddress, -) error { - - acc := ak.GetAccount(ctx, signer) - - // on InitChain make sure account number == 0 - if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 { - return sdkerrors.Wrapf( - sdkerrors.ErrInvalidSequence, - "invalid account number for height zero (got %d)", acc.GetAccountNumber(), - ) - } - - // Validate nonce is correct - if err := checkNonce(ctx, ak, ethTxMsg, signer); err != nil { - return err - } - - // validate sender has enough funds - balance := acc.GetCoins().AmountOf(emint.DenomDefault) - if balance.BigInt().Cmp(ethTxMsg.Cost()) < 0 { - return sdkerrors.Wrapf( - sdkerrors.ErrInsufficientFunds, - "%s < %s%s", balance.String(), ethTxMsg.Cost().String(), emint.DenomDefault, - ) - } - - return nil -} - -func checkNonce( - ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, signer sdk.AccAddress, -) error { - acc := ak.GetAccount(ctx, signer) - // Validate the transaction nonce is valid (equivalent to the sender account’s - // current nonce). - seq := acc.GetSequence() - if ethTxMsg.Data.AccountNonce != seq { - return sdkerrors.Wrapf( - sdkerrors.ErrInvalidSequence, - "got nonce %d, expected %d", ethTxMsg.Data.AccountNonce, seq, - ) - } - - return nil -} - -// ensureSufficientMempoolFees verifies that enough fees have been provided by the -// Ethereum transaction that meet the minimum threshold set by the block -// proposer. -// -// NOTE: This should only be ran during a CheckTx mode. -func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.MsgEthereumTx) error { - // fee = GP * GL - fee := sdk.NewDecCoinFromCoin(sdk.NewInt64Coin(emint.DenomDefault, ethTxMsg.Fee().Int64())) - - minGasPrices := ctx.MinGasPrices() - allGTE := true - for _, v := range minGasPrices { - if !fee.IsGTE(v) { - allGTE = false - } - } - - // it is assumed that the minimum fees will only include the single valid denom - if !ctx.MinGasPrices().IsZero() && !allGTE { - // reject the transaction that does not meet the minimum fee - return sdkerrors.Wrapf( - sdkerrors.ErrInsufficientFee, - "got: %q required: %q", fee, ctx.MinGasPrices(), - ) - } - - return nil -} diff --git a/app/ante/ante_test.go b/app/ante/ante_test.go index 4b3a084b..95bc0467 100644 --- a/app/ante/ante_test.go +++ b/app/ante/ante_test.go @@ -55,7 +55,7 @@ func (suite *AnteTestSuite) TestValidEthTx() { to := ethcmn.BytesToAddress(addr2.Bytes()) amt := big.NewInt(32) gas := big.NewInt(20) - ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test")) + ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 34910, gas, []byte("test")) tx := newTestEthTx(suite.ctx, ethMsg, priv1) requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) diff --git a/app/ante/doc.go b/app/ante/doc.go new file mode 100644 index 00000000..73b56f74 --- /dev/null +++ b/app/ante/doc.go @@ -0,0 +1,11 @@ +/*Package ante defines the SDK auth module's AnteHandler as well as an internal +AnteHandler for an Ethereum transaction (i.e MsgEthereumTx). + +During CheckTx, the transaction is passed through a series of +pre-message execution validation checks such as signature and account +verification in addition to minimum fees being checked. Otherwise, during +DeliverTx, the transaction is simply passed to the EVM which will also +perform the same series of checks. The distinction is made in CheckTx to +prevent spam and DoS attacks. +*/ +package ante diff --git a/app/ante/eth.go b/app/ante/eth.go new file mode 100644 index 00000000..3c777617 --- /dev/null +++ b/app/ante/eth.go @@ -0,0 +1,361 @@ +package ante + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/auth/types" + + emint "github.com/cosmos/ethermint/types" + evmtypes "github.com/cosmos/ethermint/x/evm/types" + + ethcore "github.com/ethereum/go-ethereum/core" +) + +// EthSetupContextDecorator sets the infinite GasMeter in the Context and wraps +// the next AnteHandler with a defer clause to recover from any downstream +// OutOfGas panics in the AnteHandler chain to return an error with information +// on gas provided and gas used. +// CONTRACT: Must be first decorator in the chain +// CONTRACT: Tx must implement GasTx interface +type EthSetupContextDecorator struct{} + +// NewEthSetupContextDecorator creates a new EthSetupContextDecorator +func NewEthSetupContextDecorator() EthSetupContextDecorator { + return EthSetupContextDecorator{} +} + +// AnteHandle sets the infinite gas meter to done to ignore costs in AnteHandler checks. +// This is undone at the EthGasConsumeDecorator, where the context is set with the +// ethereum tx GasLimit. +func (escd EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + + // all transactions must implement GasTx + gasTx, ok := tx.(authante.GasTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") + } + + // Decorator will catch an OutOfGasPanic caused in the next antehandler + // AnteHandlers must have their own defer/recover in order for the BaseApp + // to know how much gas was used! This is because the GasMeter is created in + // the AnteHandler, but if it panics the context won't be set properly in + // runTx's recover call. + defer func() { + if r := recover(); r != nil { + switch rType := r.(type) { + case sdk.ErrorOutOfGas: + log := fmt.Sprintf( + "out of gas in location: %v; gasLimit: %d, gasUsed: %d", + rType.Descriptor, gasTx.GetGas(), ctx.GasMeter().GasConsumed(), + ) + err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log) + default: + panic(r) + } + } + }() + + return next(ctx, tx, simulate) +} + +// EthMempoolFeeDecorator validates that sufficient fees have been provided that +// meet a minimum threshold defined by the proposer (for mempool purposes during CheckTx). +type EthMempoolFeeDecorator struct{} + +// NewEthMempoolFeeDecorator creates a new EthMempoolFeeDecorator +func NewEthMempoolFeeDecorator() EthMempoolFeeDecorator { + return EthMempoolFeeDecorator{} +} + +// AnteHandle verifies that enough fees have been provided by the +// Ethereum transaction that meet the minimum threshold set by the block +// proposer. +// +// NOTE: This should only be run during a CheckTx mode. +func (emfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !ctx.IsCheckTx() { + return next(ctx, tx, simulate) + } + + msgEthTx, ok := tx.(evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + } + + // fee = GP * GL + fee := sdk.NewInt64DecCoin(emint.DenomDefault, msgEthTx.Fee().Int64()) + + minGasPrices := ctx.MinGasPrices() + + allGTE := true + for _, v := range minGasPrices { + if !fee.IsGTE(v) { + allGTE = false + } + } + + // it is assumed that the minimum fees will only include the single valid denom + if !ctx.MinGasPrices().IsZero() && !allGTE { + // reject the transaction that does not meet the minimum fee + return ctx, sdkerrors.Wrap( + sdkerrors.ErrInsufficientFee, + fmt.Sprintf("insufficient fee, got: %q required: %q", fee, ctx.MinGasPrices()), + ) + } + + return next(ctx, tx, simulate) +} + +// EthSigVerificationDecorator validates an ethereum signature +type EthSigVerificationDecorator struct{} + +// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator +func NewEthSigVerificationDecorator() EthSigVerificationDecorator { + return EthSigVerificationDecorator{} +} + +// AnteHandle validates the signature and returns sender address +func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + msgEthTx, ok := tx.(evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + } + + // parse the chainID from a string to a base-10 integer + chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) + if !ok { + return ctx, sdkerrors.Wrap(emint.ErrInvalidChainID, ctx.ChainID()) + } + + // validate sender/signature + // NOTE: signer is retrieved from the transaction on the next AnteDecorator + _, err = msgEthTx.VerifySig(chainID) + if err != nil { + return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprintf("signature verification failed")) + } + + return next(ctx, msgEthTx, simulate) +} + +// AccountVerificationDecorator validates an account balance checks +type AccountVerificationDecorator struct { + ak auth.AccountKeeper +} + +// NewAccountVerificationDecorator creates a new AccountVerificationDecorator +func NewAccountVerificationDecorator(ak auth.AccountKeeper) AccountVerificationDecorator { + return AccountVerificationDecorator{ + ak: ak, + } +} + +// AnteHandle validates the signature and returns sender address +func (avd AccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !ctx.IsCheckTx() { + return next(ctx, tx, simulate) + } + + msgEthTx, ok := tx.(evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + } + + // sender address should be in the tx cache + address := msgEthTx.From() + if address == nil { + panic("sender address is nil") + } + + acc := avd.ak.GetAccount(ctx, address) + if acc == nil { + return ctx, fmt.Errorf("account %s is nil", address) + } + + // on InitChain make sure account number == 0 + if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrInvalidSequence, + "invalid account number for height zero (got %d)", acc.GetAccountNumber(), + ) + } + + // validate sender has enough funds + balance := acc.GetCoins().AmountOf(emint.DenomDefault) + if balance.BigInt().Cmp(msgEthTx.Cost()) < 0 { + return ctx, sdkerrors.Wrapf( + sdkerrors.ErrInsufficientFunds, + "%s < %s%s", balance.String(), msgEthTx.Cost().String(), emint.DenomDefault, + ) + } + + return next(ctx, tx, simulate) +} + +// NonceVerificationDecorator that the nonce matches +type NonceVerificationDecorator struct { + ak auth.AccountKeeper +} + +// NewNonceVerificationDecorator creates a new NonceVerificationDecorator +func NewNonceVerificationDecorator(ak auth.AccountKeeper) NonceVerificationDecorator { + return NonceVerificationDecorator{ + ak: ak, + } +} + +// AnteHandle validates that the transaction nonce is valid (equivalent to the sender account’s +// current nonce). +func (nvd NonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + msgEthTx, ok := tx.(evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + } + + // sender address should be in the tx cache + address := msgEthTx.From() + if address == nil { + panic("sender address is nil") + } + + acc := nvd.ak.GetAccount(ctx, address) + if acc == nil { + return ctx, fmt.Errorf("account %s is nil", address) + } + + seq := acc.GetSequence() + if msgEthTx.Data.AccountNonce != seq { + return ctx, sdkerrors.Wrap( + sdkerrors.ErrInvalidSequence, + fmt.Sprintf("invalid nonce; got %d, expected %d", msgEthTx.Data.AccountNonce, seq), + ) + } + + return next(ctx, tx, simulate) +} + +// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and +// gas consumption. +type EthGasConsumeDecorator struct { + ak auth.AccountKeeper + sk types.SupplyKeeper +} + +// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator +func NewEthGasConsumeDecorator(ak auth.AccountKeeper, sk types.SupplyKeeper) EthGasConsumeDecorator { + return EthGasConsumeDecorator{ + ak: ak, + sk: sk, + } +} + +// AnteHandle validates that the Ethereum tx message has enough to cover intrinsic gas +// (during CheckTx only) and that the sender has enough balance to pay for the gas cost. +// +// Intrinsic gas for a transaction is the amount of gas +// that the transaction uses before the transaction is executed. The gas is a +// constant value of 21000 plus any cost inccured by additional bytes of data +// supplied with the transaction. +func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + msgEthTx, ok := tx.(evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + } + + // sender address should be in the tx cache + address := msgEthTx.From() + if address == nil { + panic("sender address is nil") + } + + // Fetch sender account from signature + senderAcc, err := auth.GetSignerAcc(ctx, egcd.ak, address) + if err != nil { + return ctx, err + } + + if senderAcc == nil { + return ctx, fmt.Errorf("sender account %s is nil", address) + } + + gasLimit := msgEthTx.GetGas() + gas, err := ethcore.IntrinsicGas(msgEthTx.Data.Payload, msgEthTx.To() == nil, true) + if err != nil { + return ctx, sdkerrors.Wrap(err, "failed to compute intrinsic gas cost") + } + + // intrinsic gas verification during CheckTx + if ctx.IsCheckTx() && gasLimit < gas { + return ctx, fmt.Errorf("intrinsic gas too low: %d < %d", gasLimit, gas) + } + + // Charge sender for gas up to limit + if gasLimit != 0 { + // Cost calculates the fees paid to validators based on gas limit and price + cost := new(big.Int).Mul(msgEthTx.Data.Price, new(big.Int).SetUint64(gasLimit)) + + feeAmt := sdk.NewCoins( + sdk.NewCoin(emint.DenomDefault, sdk.NewIntFromBigInt(cost)), + ) + + err = auth.DeductFees(egcd.sk, ctx, senderAcc, feeAmt) + if err != nil { + return ctx, err + } + } + + // Set gas meter after ante handler to ignore gaskv costs + newCtx = auth.SetGasMeter(simulate, ctx, gasLimit) + newCtx.GasMeter().ConsumeGas(gas, "eth intrinsic gas") + + return next(newCtx, tx, simulate) +} + +// IncrementSenderSequenceDecorator increments the sequence of the signers. The +// main difference with the SDK's IncrementSequenceDecorator is that the MsgEthereumTx +// doesn't implement the SigVerifiableTx interface. +// +// CONTRACT: must be called after msg.VerifySig in order to cache the sender address. +type IncrementSenderSequenceDecorator struct { + ak auth.AccountKeeper +} + +// NewIncrementSenderSequenceDecorator creates a new IncrementSenderSequenceDecorator. +func NewIncrementSenderSequenceDecorator(ak auth.AccountKeeper) IncrementSenderSequenceDecorator { + return IncrementSenderSequenceDecorator{ + ak: ak, + } +} + +// AnteHandle handles incrementing the sequence of the sender. +func (issd IncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // no need to increment sequence on RecheckTx + if ctx.IsReCheckTx() && !simulate { + return next(ctx, tx, simulate) + } + + // get and set account must be called with an infinite gas meter in order to prevent + // additional gas from being deducted. + oldCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + + msgEthTx, ok := tx.(evmtypes.MsgEthereumTx) + if !ok { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + } + + // increment sequence of all signers + for _, addr := range msgEthTx.GetSigners() { + acc := issd.ak.GetAccount(oldCtx, addr) + if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { + panic(err) + } + issd.ak.SetAccount(oldCtx, acc) + } + + return next(ctx, tx, simulate) +} diff --git a/app/export.go b/app/export.go index 4d4b29c3..cea9a852 100644 --- a/app/export.go +++ b/app/export.go @@ -8,12 +8,18 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking/exported" ) +// NewDefaultGenesisState generates the default state for the application. +func NewDefaultGenesisState() simapp.GenesisState { + return ModuleBasics.DefaultGenesis() +} + // ExportAppStateAndValidators exports the state of the application for a genesis // file. func (app *EthermintApp) ExportAppStateAndValidators( diff --git a/app/test_helpers.go b/app/test_helpers.go index f414121b..7a9bbe07 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -6,16 +6,16 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/simapp" ) // Setup initializes a new EthermintApp. A Nop logger is set in EthermintApp. func Setup(isCheckTx bool) *EthermintApp { db := dbm.NewMemDB() app := NewEthermintApp(log.NewNopLogger(), db, nil, true, 0) + if !isCheckTx { // init chain must be called to stop deliverState from being nil - genesisState := simapp.NewDefaultGenesisState() + genesisState := NewDefaultGenesisState() stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState) if err != nil { panic(err) diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 6aa7d5c8..78f588d1 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -157,10 +157,13 @@ func (msg MsgEthereumTx) GetMsgs() []sdk.Msg { // GetSigners returns the expected signers for an Ethereum transaction message. // For such a message, there should exist only a single 'signer'. // -// NOTE: This method cannot be used as a chain ID is needed to recover the signer -// from the signature. Use 'VerifySig' instead. +// NOTE: This method panics if 'VerifySig' hasn't been called first. func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress { - panic("must use 'VerifySig' with a chain ID to get the signer") + sender := msg.From() + if sender.Empty() { + panic("must use 'VerifySig' with a chain ID to get the signer") + } + return []sdk.AccAddress{sender} } // GetSignBytes returns the Amino bytes of an Ethereum transaction message used @@ -271,11 +274,9 @@ func (msg *MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) { return sender, nil } -// Cost returns amount + gasprice * gaslimit. -func (msg MsgEthereumTx) Cost() *big.Int { - total := msg.Fee() - total.Add(total, msg.Data.Amount) - return total +// GetGas implements the GasTx interface. It returns the GasLimit of the transaction. +func (msg MsgEthereumTx) GetGas() uint64 { + return msg.Data.GasLimit } // Fee returns gasprice * gaslimit. @@ -288,6 +289,30 @@ func (msg *MsgEthereumTx) ChainID() *big.Int { return deriveChainID(msg.Data.V) } +// Cost returns amount + gasprice * gaslimit. +func (msg MsgEthereumTx) Cost() *big.Int { + total := msg.Fee() + total.Add(total, msg.Data.Amount) + return total +} + +// From loads the ethereum sender address from the sigcache and returns an +// sdk.AccAddress from its bytes +func (msg *MsgEthereumTx) From() sdk.AccAddress { + sc := msg.from.Load() + if sc == nil { + return nil + } + + sigCache := sc.(sigCache) + + if len(sigCache.from.Bytes()) == 0 { + return nil + } + + return sdk.AccAddress(sigCache.from.Bytes()) +} + // deriveChainID derives the chain id from the given v parameter func deriveChainID(v *big.Int) *big.Int { if v.BitLen() <= 64 {