From 5ba8ffe669562518a465d17ff4bc663d94008cdb Mon Sep 17 00:00:00 2001 From: Thomas Nguy <81727899+thomas-nguy@users.noreply.github.com> Date: Wed, 30 Jun 2021 18:31:30 +0900 Subject: [PATCH] ante, evm: update gas consumption logic (#178) * clean up logic by ignoring cosmos gas meter * set gas used in txResponse * reset and set the gas in context * rename ante handler * fix refundedgas logic * remove gas update logic in keeper * update context in keeper * add test for EthSetupContextDecorator * fix broken test due to gas logic change --- app/ante/ante.go | 2 +- app/ante/eth.go | 86 +++++++++++------------- app/ante/eth_test.go | 62 +++++++++++------- docs/core/proto-docs.md | 1 + ethereum/rpc/namespaces/eth/api.go | 6 +- proto/ethermint/evm/v1alpha1/tx.proto | 2 + x/evm/keeper/state_transition.go | 94 +++++++++------------------ x/evm/types/tx.pb.go | 83 +++++++++++++++-------- 8 files changed, 170 insertions(+), 166 deletions(-) diff --git a/app/ante/ante.go b/app/ante/ante.go index f0318178..45a6ed79 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -60,7 +60,7 @@ func NewAnteHandler( // handle as *evmtypes.MsgEthereumTx anteHandler = sdk.ChainAnteDecorators( - authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + NewEthSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first authante.NewMempoolFeeDecorator(), authante.NewTxTimeoutHeightDecorator(), authante.NewValidateMemoDecorator(ak), diff --git a/app/ante/eth.go b/app/ante/eth.go index 8d16c150..ac387ab7 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -56,13 +56,9 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s ) } - // get and set account must be called with an infinite gas meter in order to prevent - // additional gas from being deducted. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - chainID := esvd.evmKeeper.ChainID() - config, found := esvd.evmKeeper.GetChainConfig(infCtx) + config, found := esvd.evmKeeper.GetChainConfig(ctx) if !found { return ctx, evmtypes.ErrChainConfigNotFound } @@ -122,11 +118,8 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx 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. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - avd.evmKeeper.WithContext(infCtx) - evmDenom := avd.evmKeeper.GetParams(infCtx).EvmDenom + avd.evmKeeper.WithContext(ctx) + evmDenom := avd.evmKeeper.GetParams(ctx).EvmDenom for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) @@ -154,14 +147,14 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx "the sender is not EOA: address <%v>, codeHash <%s>", fromAddr, codeHash), "") } - acc := avd.ak.GetAccount(infCtx, from) + acc := avd.ak.GetAccount(ctx, from) if acc == nil { - acc = avd.ak.NewAccountWithAddress(infCtx, from) - avd.ak.SetAccount(infCtx, acc) + acc = avd.ak.NewAccountWithAddress(ctx, from) + avd.ak.SetAccount(ctx, acc) } // validate sender has enough funds to pay for tx cost - balance := avd.bankKeeper.GetBalance(infCtx, from, evmDenom) + balance := avd.bankKeeper.GetBalance(ctx, from, evmDenom) if balance.Amount.BigInt().Cmp(msgEthTx.Cost()) < 0 { return ctx, stacktrace.Propagate( sdkerrors.Wrapf( @@ -199,10 +192,6 @@ func (nvd EthNonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, 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. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { @@ -213,7 +202,7 @@ func (nvd EthNonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, } // sender address should be in the tx cache from the previous AnteHandle call - seq, err := nvd.ak.GetSequence(infCtx, msgEthTx.GetFrom()) + seq, err := nvd.ak.GetSequence(ctx, msgEthTx.GetFrom()) if err != nil { return ctx, stacktrace.Propagate(err, "sequence not found for address %s", msgEthTx.From) } @@ -267,14 +256,10 @@ func NewEthGasConsumeDecorator(ak AccountKeeper, bankKeeper BankKeeper, ek EVMKe // - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price) // - transaction or block gas meter runs out of gas func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // get and set account must be called with an infinite gas meter in order to prevent - // additional gas from being deducted. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - // reset the refund gas value in the keeper for the current transaction - egcd.evmKeeper.ResetRefundTransient(infCtx) + egcd.evmKeeper.ResetRefundTransient(ctx) - config, found := egcd.evmKeeper.GetChainConfig(infCtx) + config, found := egcd.evmKeeper.GetChainConfig(ctx) if !found { return ctx, evmtypes.ErrChainConfigNotFound } @@ -297,7 +282,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula isContractCreation := msgEthTx.To() == nil // fetch sender account from signature - signerAcc, err := authante.GetSignerAcc(infCtx, egcd.ak, msgEthTx.GetFrom()) + signerAcc, err := authante.GetSignerAcc(ctx, egcd.ak, msgEthTx.GetFrom()) if err != nil { return ctx, stacktrace.Propagate(err, "account not found for sender %s", msgEthTx.From) } @@ -324,20 +309,16 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // calculate the fees paid to validators based on gas limit and price feeAmt := msgEthTx.Fee() // fee = gas limit * gas price - evmDenom := egcd.evmKeeper.GetParams(infCtx).EvmDenom + evmDenom := egcd.evmKeeper.GetParams(ctx).EvmDenom fees := sdk.Coins{sdk.NewCoin(evmDenom, sdk.NewIntFromBigInt(feeAmt))} // deduct the full gas cost from the user balance - if err := authante.DeductFees(egcd.bankKeeper, infCtx, signerAcc, fees); err != nil { + if err := authante.DeductFees(egcd.bankKeeper, ctx, signerAcc, fees); err != nil { return ctx, stacktrace.Propagate( err, "failed to deduct full gas cost %s from the user %s balance", fees, msgEthTx.From, ) } - - // consume intrinsic gas for the current transaction. After runTx is executed on Baseapp, the - // application will consume gas from the block gas pool. - ctx.GasMeter().ConsumeGas(intrinsicGas, "intrinsic gas") } // TODO: deprecate after https://github.com/cosmos/cosmos-sdk/issues/9514 is fixed on SDK @@ -373,12 +354,9 @@ func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { // AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to // see if the address can execute the transaction. func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // get and set account must be called with an infinite gas meter in order to prevent - // additional gas from being deducted. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - ctd.evmKeeper.WithContext(infCtx) + ctd.evmKeeper.WithContext(ctx) - config, found := ctd.evmKeeper.GetChainConfig(infCtx) + config, found := ctd.evmKeeper.GetChainConfig(ctx) if !found { return ctx, evmtypes.ErrChainConfigNotFound } @@ -444,12 +422,8 @@ func NewAccessListDecorator(evmKeeper EVMKeeper) AccessListDecorator { // // The AnteHandler will only prepare the access list if Yolov3/Berlin/EIPs 2929 and 2930 are applicable at the current number. func (ald AccessListDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // get and set account must be called with an infinite gas meter in order to prevent - // additional gas from being deducted. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - - config, found := ald.evmKeeper.GetChainConfig(infCtx) + config, found := ald.evmKeeper.GetChainConfig(ctx) if !found { return ctx, evmtypes.ErrChainConfigNotFound } @@ -464,7 +438,7 @@ func (ald AccessListDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b } // setup the keeper context before setting the access list - ald.evmKeeper.WithContext(infCtx) + ald.evmKeeper.WithContext(ctx) for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) @@ -501,9 +475,6 @@ func NewEthIncrementSenderSequenceDecorator(ak AccountKeeper) EthIncrementSender // contract creation, the nonce will be incremented during the transaction execution and not within // this AnteHandler decorator. func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // get and set account must be called with an infinite gas meter in order to prevent - // additional gas from being deducted. - infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) @@ -524,7 +495,7 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s // increment sequence of all signers for _, addr := range msg.GetSigners() { - acc := issd.ak.GetAccount(infCtx, addr) + acc := issd.ak.GetAccount(ctx, addr) if acc == nil { return ctx, stacktrace.Propagate( @@ -540,7 +511,7 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s return ctx, stacktrace.Propagate(err, "failed to set sequence to %d", acc.GetSequence()+1) } - issd.ak.SetAccount(infCtx, acc) + issd.ak.SetAccount(ctx, acc) } } @@ -571,3 +542,22 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu return next(ctx, tx, simulate) } + +// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption +// by setting the gas meter to infinite +type EthSetupContextDecorator struct{} + +func NewEthSetUpContextDecorator() EthSetupContextDecorator { + return EthSetupContextDecorator{} +} + +func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // all transactions must implement GasTx + _, ok := tx.(authante.GasTx) + if !ok { + return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") + } + + newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + return next(newCtx, tx, simulate) +} diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index 08296449..2aea12ac 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" ) func nextFn(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { @@ -46,9 +45,7 @@ func (suite AnteTestSuite) TestEthSigVerificationDecorator() { for _, tc := range testCases { suite.Run(tc.name, func() { - consumed := suite.ctx.GasMeter().GasConsumed() - ctx, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, nextFn) - suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + _, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) @@ -133,10 +130,7 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() { for _, tc := range testCases { suite.Run(tc.name, func() { tc.malleate() - - consumed := suite.ctx.GasMeter().GasConsumed() - ctx, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(tc.checkTx), tc.tx, false, nextFn) - suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + _, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(tc.checkTx), tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) @@ -193,9 +187,7 @@ func (suite AnteTestSuite) TestEthNonceVerificationDecorator() { suite.Run(tc.name, func() { tc.malleate() - consumed := suite.ctx.GasMeter().GasConsumed() - ctx, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, nextFn) - suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + _, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) @@ -303,11 +295,9 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() { return } - consumed := suite.ctx.GasMeter().GasConsumed() - ctx, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn) + _, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) - suite.Require().Equal(int(params.TxGasContractCreation+params.TxAccessListAddressGas), int(ctx.GasMeter().GasConsumed()-consumed)) } else { suite.Require().Error(err) } @@ -364,9 +354,7 @@ func (suite AnteTestSuite) TestCanTransferDecorator() { tc.malleate() - consumed := suite.ctx.GasMeter().GasConsumed() - ctx, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn) - suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + _, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) @@ -426,10 +414,7 @@ func (suite AnteTestSuite) TestAccessListDecorator() { suite.Run(tc.name, func() { tc.malleate() - - consumed := suite.ctx.GasMeter().GasConsumed() - ctx, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn) - suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + _, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) @@ -503,7 +488,6 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { suite.Run(tc.name, func() { tc.malleate() - consumed := suite.ctx.GasMeter().GasConsumed() if tc.expPanic { suite.Require().Panics(func() { @@ -512,8 +496,7 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { return } - ctx, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn) - suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed()) + _, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn) if tc.expPass { suite.Require().NoError(err) @@ -530,3 +513,34 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { }) } } + +func (suite AnteTestSuite) TestEthSetupContextDecorator() { + dec := ante.NewEthSetUpContextDecorator() + tx := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil) + + testCases := []struct { + name string + tx sdk.Tx + expPass bool + }{ + {"invalid transaction type - does not implement GasTx", &invalidTx{}, false}, + { + "success - transaction implement GasTx", + tx, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + _, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + + }) + } +} diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 2a09b67b..c9c7cd1e 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -827,6 +827,7 @@ MsgEthereumTxResponse defines the Msg/EthereumTx response type. | `logs` | [Log](#ethermint.evm.v1alpha1.Log) | repeated | logs contains the transaction hash and the proto-compatible ethereum logs. | | `ret` | [bytes](#bytes) | | returned data from evm function (result or data supplied with revert opcode) | | `reverted` | [bool](#bool) | | reverted flag is set to true when the call has been reverted | +| `gas_used` | [uint64](#uint64) | | gas consumed by the transaction | diff --git a/ethereum/rpc/namespaces/eth/api.go b/ethereum/rpc/namespaces/eth/api.go index 7fe3cbd7..70538bfd 100644 --- a/ethereum/rpc/namespaces/eth/api.go +++ b/ethereum/rpc/namespaces/eth/api.go @@ -667,11 +667,7 @@ func (e *PublicAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, error) return 0, rpctypes.ErrRevertedWith(data.Ret) } - // TODO: Add Gas Info from state transition to MsgEthereumTxResponse fields and return that instead - estimatedGas := simRes.GasInfo.GasUsed - gas := estimatedGas + 200000 - - return hexutil.Uint64(gas), nil + return hexutil.Uint64(data.GasUsed), nil } // GetBlockByHash returns the block identified by hash. diff --git a/proto/ethermint/evm/v1alpha1/tx.proto b/proto/ethermint/evm/v1alpha1/tx.proto index 073b6297..c88f4b62 100644 --- a/proto/ethermint/evm/v1alpha1/tx.proto +++ b/proto/ethermint/evm/v1alpha1/tx.proto @@ -52,4 +52,6 @@ message MsgEthereumTxResponse { bytes ret = 3; // reverted flag is set to true when the call has been reverted bool reverted = 4; + // gas consumed by the transaction + uint64 gas_used = 5; } \ No newline at end of file diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index de355ec6..c57b983e 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -112,12 +112,7 @@ func (k Keeper) GetHashFn() vm.GetHashFunc { func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB) - gasMeter := k.ctx.GasMeter() // tx gas meter - - // ignore gas consumption costs - infCtx := k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - - cfg, found := k.GetChainConfig(infCtx) + cfg, found := k.GetChainConfig(k.ctx) if !found { return nil, stacktrace.Propagate(types.ErrChainConfigNotFound, "configuration not found") } @@ -135,9 +130,6 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT k.IncreaseTxIndexTransient() - // set the original gas meter to apply the message and perform the state transition - - k.WithContext(k.ctx.WithGasMeter(gasMeter)) // create an ethereum StateTransition instance and run TransitionDb res, err := k.ApplyMessage(evm, msg, ethCfg) if err != nil { @@ -157,6 +149,7 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs))) k.SetBlockBloomTransient(bloom) + k.resetGasMeterAndConsumeGas(res.GasUsed) return res, nil } @@ -209,24 +202,13 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo sender := vm.AccountRef(msg.From()) contractCreation := msg.To() == nil - // transaction gas meter (tracks limit and usage) - gasConsumed := k.ctx.GasMeter().GasConsumed() - leftoverGas := k.ctx.GasMeter().Limit() - k.ctx.GasMeter().GasConsumedToLimit() - - // NOTE: Since CRUD operations on the SDK store consume gas we need to set up an infinite gas meter so that we only consume - // the gas used by the Ethereum message execution. - // Not setting the infinite gas meter here would mean that we are incurring in additional gas costs - k.WithContext(k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter())) - - // NOTE: gas limit is the GasLimit defied in the message minus the Intrinsic Gas that has already been - // consumed on the AnteHandler. - - // ensure gas is consistent during CheckTx - if k.ctx.IsCheckTx() { - if err := k.CheckGasConsumption(msg, cfg, gasConsumed, contractCreation); err != nil { - return nil, stacktrace.Propagate(err, "gas consumption check failed during CheckTx") - } + intrinsicGas, err := k.GetEthIntrinsicGas(msg, cfg, contractCreation) + if err != nil { + // should have already been checked on Ante Handler + return nil, stacktrace.Propagate(err, "intrinsic gas failed") } + // should be > 0 as it is checked on Ante Handler + leftoverGas := msg.Gas() - intrinsicGas if contractCreation { ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value()) @@ -235,7 +217,8 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo } // refund gas prior to handling the vm error in order to set the updated gas meter - if err := k.RefundGas(msg, leftoverGas); err != nil { + leftoverGas, err = k.RefundGas(msg, leftoverGas) + if err != nil { return nil, stacktrace.Propagate(err, "failed to refund gas leftover gas to sender %s", msg.From()) } @@ -249,38 +232,30 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo return nil, stacktrace.Propagate(sdkerrors.Wrap(types.ErrVMExecution, vmErr.Error()), "vm execution failed") } + gasUsed := msg.Gas() - leftoverGas return &types.MsgEthereumTxResponse{ Ret: ret, Reverted: false, + GasUsed: gasUsed, }, nil } -// CheckGasConsumption verifies that the amount of gas consumed so far matches the intrinsic gas value. -func (k *Keeper) CheckGasConsumption(msg core.Message, cfg *params.ChainConfig, gasConsumed uint64, isContractCreation bool) error { +// GetEthIntrinsicGas get the transaction intrinsic gas cost +func (k *Keeper) GetEthIntrinsicGas(msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) { height := big.NewInt(k.ctx.BlockHeight()) homestead := cfg.IsHomestead(height) istanbul := cfg.IsIstanbul(height) - intrinsicGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), isContractCreation, homestead, istanbul) - if err != nil { - // should have already been checked on Ante Handler - return stacktrace.Propagate(err, "intrinsic gas failed") - } - - if intrinsicGas != gasConsumed { - return sdkerrors.Wrapf(types.ErrInconsistentGas, "expected gas consumption to be %d (intrinsic gas only), got %d", intrinsicGas, gasConsumed) - } - - return nil + return core.IntrinsicGas(msg.Data(), msg.AccessList(), isContractCreation, homestead, istanbul) } // RefundGas transfers the leftover gas to the sender of the message, caped to half of the total gas // consumed in the transaction. Additionally, the function sets the total gas consumed to the value -// returned by the EVM execution, thus ignoring the previous intrinsic gas inconsumed during in the +// returned by the EVM execution, thus ignoring the previous intrinsic gas consumed during in the // AnteHandler. -func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64) error { +func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64) (uint64, error) { if leftoverGas > msg.Gas() { - return stacktrace.Propagate( + return leftoverGas, stacktrace.Propagate( sdkerrors.Wrapf(types.ErrInconsistentGas, "leftover gas cannot be greater than gas limit (%d > %d)", leftoverGas, msg.Gas()), "failed to update gas consumed after refund of leftover gas", ) @@ -298,47 +273,42 @@ func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64) error { leftoverGas += refund if leftoverGas > msg.Gas() { - return stacktrace.Propagate( + return leftoverGas, stacktrace.Propagate( sdkerrors.Wrapf(types.ErrInconsistentGas, "leftover gas cannot be greater than gas limit (%d > %d)", leftoverGas, msg.Gas()), "failed to update gas consumed after refund of %d gas", refund, ) } - gasConsumed = msg.Gas() - leftoverGas - // Return EVM tokens for remaining gas, exchanged at the original rate. remaining := new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), msg.GasPrice()) - // ignore gas consumption - infCtx := k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - switch remaining.Sign() { case -1: // negative refund errors - return sdkerrors.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", remaining.Int64()) + return leftoverGas, sdkerrors.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", remaining.Int64()) case 1: // positive amount refund - params := k.GetParams(infCtx) + params := k.GetParams(k.ctx) refundedCoins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(remaining))} // refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees - err := k.bankKeeper.SendCoinsFromModuleToAccount(infCtx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins) + err := k.bankKeeper.SendCoinsFromModuleToAccount(k.ctx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins) if err != nil { err = sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error()) - return stacktrace.Propagate(err, "failed to refund %d leftover gas (%s)", leftoverGas, refundedCoins.String()) + return leftoverGas, stacktrace.Propagate(err, "failed to refund %d leftover gas (%s)", leftoverGas, refundedCoins.String()) } default: // no refund, consume gas and update the tx gas meter } - // set the gas consumed into the context with the new gas meter. This gas meter will have the - // original gas limit defined in the msg and will consume the gas now that the amount has been - // refunded - gasMeter := sdk.NewGasMeter(msg.Gas()) - // NOTE: gas consumed will always be less than the limit - gasMeter.ConsumeGas(gasConsumed, "update gas consumption after refund") - k.WithContext(k.ctx.WithGasMeter(gasMeter)) - - return nil + return leftoverGas, nil +} + +// resetGasMeterAndConsumeGas reset first the gas meter consumed value to zero and set it back to the new value +// 'gasUsed' +func (k *Keeper) resetGasMeterAndConsumeGas(gasUsed uint64) { + // reset the gas count + k.ctx.GasMeter().RefundGas(k.ctx.GasMeter().GasConsumed(), "reset the gas count") + k.ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction") } diff --git a/x/evm/types/tx.pb.go b/x/evm/types/tx.pb.go index a4987726..bddc26bb 100644 --- a/x/evm/types/tx.pb.go +++ b/x/evm/types/tx.pb.go @@ -160,6 +160,8 @@ type MsgEthereumTxResponse struct { Ret []byte `protobuf:"bytes,3,opt,name=ret,proto3" json:"ret,omitempty"` // reverted flag is set to true when the call has been reverted Reverted bool `protobuf:"varint,4,opt,name=reverted,proto3" json:"reverted,omitempty"` + // gas consumed by the transaction + GasUsed uint64 `protobuf:"varint,5,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` } func (m *MsgEthereumTxResponse) Reset() { *m = MsgEthereumTxResponse{} } @@ -205,32 +207,34 @@ func init() { func init() { proto.RegisterFile("ethermint/evm/v1alpha1/tx.proto", fileDescriptor_6a305e80b084ab0e) } var fileDescriptor_6a305e80b084ab0e = []byte{ - // 400 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0xeb, 0xd3, 0x30, - 0x18, 0xc6, 0x9b, 0xb5, 0xea, 0xcc, 0x14, 0x24, 0xe8, 0xa8, 0x15, 0xd2, 0x52, 0x10, 0x7a, 0x59, - 0xcb, 0xba, 0xdb, 0x8e, 0xc5, 0xdd, 0x1c, 0x42, 0x18, 0x08, 0xde, 0x52, 0x17, 0xdb, 0xc2, 0xda, - 0x94, 0x24, 0x2b, 0xd5, 0x4f, 0xe0, 0x71, 0x57, 0x6f, 0x7e, 0x1c, 0x8f, 0x3b, 0x7a, 0x1a, 0xb2, - 0xdd, 0x3c, 0xfa, 0x09, 0xa4, 0x99, 0xdb, 0x9c, 0x38, 0xf8, 0xdf, 0xde, 0xf6, 0xfd, 0x25, 0xcf, - 0xf3, 0x84, 0x07, 0xba, 0x4c, 0xe5, 0x4c, 0x94, 0x45, 0xa5, 0x22, 0xd6, 0x94, 0x51, 0x33, 0xa6, - 0xab, 0x3a, 0xa7, 0xe3, 0x48, 0xb5, 0x61, 0x2d, 0xb8, 0xe2, 0x68, 0x78, 0x06, 0x42, 0xd6, 0x94, - 0xe1, 0x09, 0x70, 0x9e, 0x66, 0x3c, 0xe3, 0x1a, 0x89, 0xba, 0xe9, 0x48, 0x3b, 0xde, 0x8d, 0xeb, - 0xba, 0xa3, 0x9a, 0xf0, 0xbf, 0x00, 0xf8, 0x78, 0x2e, 0xb3, 0x59, 0xc7, 0xb1, 0x75, 0xb9, 0x68, - 0x51, 0x0c, 0xad, 0x25, 0x55, 0xd4, 0x06, 0x1e, 0x08, 0x06, 0x31, 0x0e, 0xff, 0x2f, 0x18, 0x2e, - 0xda, 0x57, 0x54, 0x51, 0xa2, 0x59, 0xf4, 0x1c, 0x5a, 0xb2, 0xf8, 0xc4, 0xec, 0x9e, 0x07, 0x02, - 0x90, 0xdc, 0xfb, 0xb9, 0x73, 0xc1, 0x88, 0xe8, 0x5f, 0xc8, 0x85, 0x56, 0x4e, 0x65, 0x6e, 0x9b, - 0x1e, 0x08, 0x1e, 0x26, 0x83, 0x5f, 0x3b, 0xf7, 0x81, 0x58, 0xd5, 0x53, 0x7f, 0xe4, 0x13, 0xbd, - 0x40, 0x08, 0x5a, 0x1f, 0x04, 0x2f, 0x6d, 0xab, 0x03, 0x88, 0x9e, 0xa7, 0xd6, 0xe7, 0xaf, 0xae, - 0xe1, 0xfb, 0xd0, 0x99, 0xb5, 0x8a, 0x55, 0xb2, 0xe0, 0xd5, 0x9b, 0x5a, 0x15, 0xbc, 0x92, 0x17, - 0x9f, 0x7f, 0x18, 0x0c, 0x87, 0xff, 0x32, 0x6f, 0x59, 0x3a, 0x39, 0xef, 0x37, 0x00, 0x3e, 0xbb, - 0xca, 0x47, 0x98, 0xac, 0x79, 0x25, 0x59, 0xa7, 0xab, 0x8d, 0x81, 0xa3, 0xae, 0xf6, 0x12, 0x41, - 0x6b, 0xc5, 0x33, 0x69, 0xf7, 0x3c, 0x33, 0x18, 0xc4, 0x2f, 0x6e, 0x65, 0x7f, 0xcd, 0x33, 0xa2, - 0x41, 0xf4, 0x04, 0x9a, 0x82, 0x29, 0x1d, 0xee, 0x11, 0xe9, 0x46, 0xe4, 0xc0, 0xbe, 0x60, 0x0d, - 0x13, 0x8a, 0x2d, 0x75, 0xa4, 0x3e, 0x39, 0x7f, 0x1f, 0x2d, 0xc5, 0x05, 0x34, 0xe7, 0x32, 0x43, - 0x29, 0x84, 0x7f, 0xbd, 0xfa, 0xcb, 0x5b, 0x5a, 0x57, 0xe6, 0x9d, 0xd1, 0x9d, 0xb0, 0x53, 0xc6, - 0x24, 0xf9, 0xb6, 0xc7, 0x60, 0xbb, 0xc7, 0xe0, 0xc7, 0x1e, 0x83, 0xcd, 0x01, 0x1b, 0xdb, 0x03, - 0x36, 0xbe, 0x1f, 0xb0, 0xf1, 0x2e, 0xc8, 0x0a, 0x95, 0xaf, 0xd3, 0xf0, 0x3d, 0x2f, 0x23, 0x95, - 0x53, 0x21, 0x0b, 0x19, 0x5d, 0xca, 0xd2, 0xea, 0xba, 0xa8, 0x8f, 0x35, 0x93, 0xe9, 0x7d, 0x5d, - 0x94, 0xc9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4e, 0x06, 0x55, 0xd8, 0x9b, 0x02, 0x00, 0x00, + // 426 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x41, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0x3b, 0xdb, 0xac, 0x5b, 0x5f, 0x15, 0x64, 0xd0, 0x25, 0x1b, 0x21, 0x09, 0x01, 0x21, + 0x97, 0x26, 0x6c, 0xf7, 0xb6, 0xc7, 0xe2, 0xde, 0x5c, 0x84, 0x61, 0x45, 0xf0, 0x22, 0x53, 0xfb, + 0x9c, 0x04, 0x9a, 0x4c, 0x98, 0x37, 0x2d, 0xd1, 0x4f, 0xe0, 0xd1, 0xab, 0x37, 0xef, 0x7e, 0x11, + 0x8f, 0x7b, 0xf4, 0xb4, 0x48, 0x7b, 0xf3, 0xe8, 0x27, 0x90, 0x4c, 0xdd, 0xae, 0x2b, 0x16, 0xbc, + 0xbd, 0xe4, 0xfd, 0x66, 0xde, 0xff, 0xc7, 0x3c, 0x88, 0xd0, 0x16, 0x68, 0xaa, 0xb2, 0xb6, 0x39, + 0x2e, 0xab, 0x7c, 0x79, 0x2c, 0xe7, 0x4d, 0x21, 0x8f, 0x73, 0xdb, 0x66, 0x8d, 0xd1, 0x56, 0xf3, + 0xc3, 0x2d, 0x90, 0xe1, 0xb2, 0xca, 0xae, 0x81, 0xe0, 0xa1, 0xd2, 0x4a, 0x3b, 0x24, 0xef, 0xaa, + 0x0d, 0x1d, 0xc4, 0x3b, 0xae, 0xeb, 0x8e, 0x3a, 0x22, 0xf9, 0xc4, 0xe0, 0xfe, 0x39, 0xa9, 0xb3, + 0x8e, 0xc3, 0x45, 0x75, 0xd1, 0xf2, 0x31, 0x78, 0x33, 0x69, 0xa5, 0xcf, 0x62, 0x96, 0x0e, 0xc7, + 0x61, 0xf6, 0xef, 0x81, 0xd9, 0x45, 0xfb, 0x54, 0x5a, 0x29, 0x1c, 0xcb, 0x8f, 0xc0, 0xa3, 0xf2, + 0x3d, 0xfa, 0x7b, 0x31, 0x4b, 0xd9, 0x64, 0xff, 0xc7, 0x55, 0xc4, 0x46, 0xc2, 0xfd, 0xe2, 0x11, + 0x78, 0x85, 0xa4, 0xc2, 0xef, 0xc7, 0x2c, 0xbd, 0x3b, 0x19, 0xfe, 0xbc, 0x8a, 0x0e, 0xcc, 0xbc, + 0x39, 0x4d, 0x46, 0x89, 0x70, 0x0d, 0xce, 0xc1, 0x7b, 0x6b, 0x74, 0xe5, 0x7b, 0x1d, 0x20, 0x5c, + 0x7d, 0xea, 0x7d, 0xf8, 0x1c, 0xf5, 0x92, 0x04, 0x82, 0xb3, 0xd6, 0x62, 0x4d, 0xa5, 0xae, 0x9f, + 0x37, 0xb6, 0xd4, 0x35, 0xdd, 0xe4, 0xfc, 0xcd, 0x84, 0x70, 0xf8, 0x37, 0xf3, 0x12, 0xa7, 0x27, + 0xdb, 0xfe, 0x17, 0x06, 0x8f, 0x6e, 0xf9, 0x09, 0xa4, 0x46, 0xd7, 0x84, 0xdd, 0x5c, 0x17, 0x8c, + 0x6d, 0xe6, 0xba, 0x2c, 0x39, 0x78, 0x73, 0xad, 0xc8, 0xdf, 0x8b, 0xfb, 0xe9, 0x70, 0xfc, 0x78, + 0x97, 0xfb, 0x33, 0xad, 0x84, 0x03, 0xf9, 0x03, 0xe8, 0x1b, 0xb4, 0x4e, 0xee, 0x9e, 0xe8, 0x4a, + 0x1e, 0xc0, 0xc0, 0xe0, 0x12, 0x8d, 0xc5, 0x99, 0x53, 0x1a, 0x88, 0xed, 0x37, 0x3f, 0x82, 0x81, + 0x92, 0xf4, 0x7a, 0x41, 0x38, 0xf3, 0xf7, 0x63, 0x96, 0x7a, 0xe2, 0x40, 0x49, 0x7a, 0x41, 0x38, + 0xdb, 0xa4, 0x1d, 0x97, 0xd0, 0x3f, 0x27, 0xc5, 0xa7, 0x00, 0x7f, 0x3c, 0xc8, 0x93, 0x5d, 0x31, + 0x6e, 0x79, 0x05, 0xa3, 0xff, 0xc2, 0xae, 0xf5, 0x27, 0x93, 0xaf, 0xab, 0x90, 0x5d, 0xae, 0x42, + 0xf6, 0x7d, 0x15, 0xb2, 0x8f, 0xeb, 0xb0, 0x77, 0xb9, 0x0e, 0x7b, 0xdf, 0xd6, 0x61, 0xef, 0x55, + 0xaa, 0x4a, 0x5b, 0x2c, 0xa6, 0xd9, 0x1b, 0x5d, 0xe5, 0xb6, 0x90, 0x86, 0x4a, 0xca, 0x6f, 0xf6, + 0xa8, 0x75, 0x9b, 0x64, 0xdf, 0x35, 0x48, 0xd3, 0x3b, 0x6e, 0x87, 0x4e, 0x7e, 0x05, 0x00, 0x00, + 0xff, 0xff, 0x0f, 0x24, 0xe9, 0xff, 0xb6, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -436,6 +440,11 @@ func (m *MsgEthereumTxResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.GasUsed != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.GasUsed)) + i-- + dAtA[i] = 0x28 + } if m.Reverted { i-- if m.Reverted { @@ -553,6 +562,9 @@ func (m *MsgEthereumTxResponse) Size() (n int) { if m.Reverted { n += 2 } + if m.GasUsed != 0 { + n += 1 + sovTx(uint64(m.GasUsed)) + } return n } @@ -981,6 +993,25 @@ func (m *MsgEthereumTxResponse) Unmarshal(dAtA []byte) error { } } m.Reverted = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field GasUsed", wireType) + } + m.GasUsed = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.GasUsed |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:])