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
This commit is contained in:
Federico Kunze 2020-04-17 18:32:01 -04:00 committed by GitHub
parent c30205cae9
commit 5b2f068a03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 428 additions and 258 deletions

View File

@ -1,9 +1,6 @@
package ante package ante
import ( import (
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
@ -11,11 +8,8 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/ethermint/crypto" "github.com/cosmos/ethermint/crypto"
emint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types" evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcore "github.com/ethereum/go-ethereum/core"
tmcrypto "github.com/tendermint/tendermint/crypto" tmcrypto "github.com/tendermint/tendermint/crypto"
) )
@ -34,10 +28,10 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle
return func( return func(
ctx sdk.Context, tx sdk.Tx, sim bool, ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) { ) (newCtx sdk.Context, err error) {
var anteHandler sdk.AnteHandler
switch castTx := tx.(type) { switch tx.(type) {
case auth.StdTx: case auth.StdTx:
stdAnte := sdk.ChainAnteDecorators( anteHandler = sdk.ChainAnteDecorators(
authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
authante.NewMempoolFeeDecorator(), authante.NewMempoolFeeDecorator(),
authante.NewValidateBasicDecorator(), authante.NewValidateBasicDecorator(),
@ -51,14 +45,21 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle
authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator
) )
return stdAnte(ctx, tx, sim)
case evmtypes.MsgEthereumTx: 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: default:
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) 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) 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 accounts
// 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
}

View File

@ -55,7 +55,7 @@ func (suite *AnteTestSuite) TestValidEthTx() {
to := ethcmn.BytesToAddress(addr2.Bytes()) to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32) amt := big.NewInt(32)
gas := big.NewInt(20) 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) tx := newTestEthTx(suite.ctx, ethMsg, priv1)
requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false) requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)

11
app/ante/doc.go Normal file
View File

@ -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

361
app/ante/eth.go Normal file
View File

@ -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 accounts
// 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)
}

View File

@ -8,12 +8,18 @@ import (
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/exported" "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 // ExportAppStateAndValidators exports the state of the application for a genesis
// file. // file.
func (app *EthermintApp) ExportAppStateAndValidators( func (app *EthermintApp) ExportAppStateAndValidators(

View File

@ -6,16 +6,16 @@ import (
dbm "github.com/tendermint/tm-db" dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
) )
// Setup initializes a new EthermintApp. A Nop logger is set in EthermintApp. // Setup initializes a new EthermintApp. A Nop logger is set in EthermintApp.
func Setup(isCheckTx bool) *EthermintApp { func Setup(isCheckTx bool) *EthermintApp {
db := dbm.NewMemDB() db := dbm.NewMemDB()
app := NewEthermintApp(log.NewNopLogger(), db, nil, true, 0) app := NewEthermintApp(log.NewNopLogger(), db, nil, true, 0)
if !isCheckTx { if !isCheckTx {
// init chain must be called to stop deliverState from being nil // init chain must be called to stop deliverState from being nil
genesisState := simapp.NewDefaultGenesisState() genesisState := NewDefaultGenesisState()
stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState) stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -157,10 +157,13 @@ func (msg MsgEthereumTx) GetMsgs() []sdk.Msg {
// GetSigners returns the expected signers for an Ethereum transaction message. // GetSigners returns the expected signers for an Ethereum transaction message.
// For such a message, there should exist only a single 'signer'. // 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 // NOTE: This method panics if 'VerifySig' hasn't been called first.
// from the signature. Use 'VerifySig' instead.
func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress { 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 // 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 return sender, nil
} }
// Cost returns amount + gasprice * gaslimit. // GetGas implements the GasTx interface. It returns the GasLimit of the transaction.
func (msg MsgEthereumTx) Cost() *big.Int { func (msg MsgEthereumTx) GetGas() uint64 {
total := msg.Fee() return msg.Data.GasLimit
total.Add(total, msg.Data.Amount)
return total
} }
// Fee returns gasprice * gaslimit. // Fee returns gasprice * gaslimit.
@ -288,6 +289,30 @@ func (msg *MsgEthereumTx) ChainID() *big.Int {
return deriveChainID(msg.Data.V) 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 // deriveChainID derives the chain id from the given v parameter
func deriveChainID(v *big.Int) *big.Int { func deriveChainID(v *big.Int) *big.Int {
if v.BitLen() <= 64 { if v.BitLen() <= 64 {