fix: GasMeter reset in AnteHandler EthGasConsumeDecorator (#964)

* Fix GasMeter reset in AnteHandler - EthGasConsumeDecorator

Conforming to the spec:
e7066c4271/docs/basics/gas-fees.md
> Set newCtx.GasMeter to 0, with a limit of GasWanted.
> This step is extremely important, as it not only makes sure the transaction cannot consume infinite gas,
> but also that ctx.GasMeter is reset in-between each DeliverTx
> (ctx is set to newCtx after anteHandler is run, and the anteHandler is run each time DeliverTx is called).

* Compute gasWanted in ante handler based on msg gas

* Tests - check gas meter limit after EthGasConsumeDecorator ante handler runs

* Update CHANGELOG.md

* EthGasConsumeDecorator ante handler resets the gas meter only for CheckTx

* Reset the gas meter in Keeper.EthereumTx to an infinite gas meter

* Fix TestOutOfGasWhenDeployContract error check

* Move gas meter reset to the innermost EthAnteHandle

* add NewInfiniteGasMeterWithLimit for setting the user provided gas limit

Fixes block's consumed gas calculation in the block creation phase.

* Fix lint

* Fix lint

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
Loredana Cirstea 2022-03-06 16:09:28 +02:00 committed by GitHub
parent 805b2eada5
commit 77d9e29923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 8 deletions

View File

@ -40,9 +40,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes
* (rpc) [\#529](https://github.com/tharsis/ethermint/pull/975) Fix unexpected `nil` values for `reward`, returned by `EffectiveGasTipValue(blockBaseFee)` in the `eth_feeHistory` RPC method.
* (evm) [\#529](https://github.com/tharsis/ethermint/issues/529) Add support return value on trace tx response.
* (rpc) [#970] (https://github.com/tharsis/ethermint/pull/970) Fix unexpected nil reward values on `eth_feeHistory` response
* (ante) [tharsis#964](https://github.com/tharsis/ethermint/pull/964) add NewInfiniteGasMeterWithLimit for storing the user provided gas limit. Fixes block's consumed gas calculation in the block creation phase.
* (rpc) [tharsis#975](https://github.com/tharsis/ethermint/pull/975) Fix unexpected `nil` values for `reward`, returned by `EffectiveGasTipValue(blockBaseFee)` in the `eth_feeHistory` RPC method.
* (rpc) [tharsis#970] (https://github.com/tharsis/ethermint/pull/970) Fix unexpected nil reward values on `eth_feeHistory` response
* (evm) [tharsis#529](https://github.com/tharsis/ethermint/issues/529) support return value on trace tx response.
## Improvements

View File

@ -159,6 +159,7 @@ func NewEthGasConsumeDecorator(
// - transaction's gas limit is lower than the intrinsic gas
// - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
// - transaction or block gas meter runs out of gas
// - sets the gas meter limit
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
params := egcd.evmKeeper.GetParams(ctx)
@ -169,6 +170,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
istanbul := ethCfg.IsIstanbul(blockHeight)
london := ethCfg.IsLondon(blockHeight)
evmDenom := params.EvmDenom
gasWanted := uint64(0)
var events sdk.Events
@ -182,6 +184,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
if err != nil {
return ctx, sdkerrors.Wrap(err, "failed to unpack tx data")
}
gasWanted += txData.GetGas()
fees, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
ctx,
@ -213,6 +216,11 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
gasPool.ConsumeGas(ctx.GasMeter().GasConsumedToLimit(), "gas pool check")
}
// Set ctx.GasMeter with a limit of GasWanted (gasLimit)
gasConsumed := ctx.GasMeter().GasConsumed()
ctx = ctx.WithGasMeter(ethermint.NewInfiniteGasMeterWithLimit(gasWanted))
ctx.GasMeter().ConsumeGas(gasConsumed, "copy gas consumed")
// we know that we have enough gas on the pool to cover the intrinsic gas
return next(ctx, tx, simulate)
}

View File

@ -205,10 +205,12 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
addr := tests.GenerateAddress()
tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
txGasLimit := uint64(1000)
tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), txGasLimit, big.NewInt(1), nil, nil, nil, nil)
tx.From = addr.Hex()
tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000000, big.NewInt(1), nil, nil, nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}})
tx2GasLimit := uint64(1000000)
tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, big.NewInt(1), nil, nil, nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}})
tx2.From = addr.Hex()
var vmdb *statedb.StateDB
@ -216,32 +218,37 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
testCases := []struct {
name string
tx sdk.Tx
gasLimit uint64
malleate func()
expPass bool
expPanic bool
}{
{"invalid transaction type", &invalidTx{}, func() {}, false, false},
{"invalid transaction type", &invalidTx{}, 0, func() {}, false, false},
{
"sender not found",
evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil),
0,
func() {},
false, false,
},
{
"gas limit too low",
tx,
0,
func() {},
false, false,
},
{
"not enough balance for fees",
tx2,
0,
func() {},
false, false,
},
{
"not enough tx gas",
tx2,
0,
func() {
vmdb.AddBalance(addr, big.NewInt(1000000))
},
@ -250,6 +257,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
{
"not enough block gas",
tx2,
0,
func() {
vmdb.AddBalance(addr, big.NewInt(1000000))
@ -260,6 +268,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
{
"success",
tx2,
tx2GasLimit,
func() {
vmdb.AddBalance(addr, big.NewInt(1000000))
@ -282,12 +291,13 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
return
}
_, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn)
ctx, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewInfiniteGasMeter()), tc.tx, false, nextFn)
if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
suite.Require().Equal(tc.gasLimit, ctx.GasMeter().Limit())
})
}
}

91
types/gasmeter.go Normal file
View File

@ -0,0 +1,91 @@
package types
import (
fmt "fmt"
math "math"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a
// negative gas consumed amount.
// Copied from cosmos-sdk
type ErrorNegativeGasConsumed struct {
Descriptor string
}
// ErrorGasOverflow defines an error thrown when an action results gas consumption
// unsigned integer overflow.
type ErrorGasOverflow struct {
Descriptor string
}
type infiniteGasMeterWithLimit struct {
consumed sdk.Gas
limit sdk.Gas
}
// NewInfiniteGasMeterWithLimit returns a reference to a new infiniteGasMeter.
func NewInfiniteGasMeterWithLimit(limit sdk.Gas) sdk.GasMeter {
return &infiniteGasMeterWithLimit{
consumed: 0,
limit: limit,
}
}
func (g *infiniteGasMeterWithLimit) GasConsumed() sdk.Gas {
return g.consumed
}
func (g *infiniteGasMeterWithLimit) GasConsumedToLimit() sdk.Gas {
return g.consumed
}
func (g *infiniteGasMeterWithLimit) Limit() sdk.Gas {
return g.limit
}
// addUint64Overflow performs the addition operation on two uint64 integers and
// returns a boolean on whether or not the result overflows.
func addUint64Overflow(a, b uint64) (uint64, bool) {
if math.MaxUint64-a < b {
return 0, true
}
return a + b, false
}
func (g *infiniteGasMeterWithLimit) ConsumeGas(amount sdk.Gas, descriptor string) {
var overflow bool
// TODO: Should we set the consumed field after overflow checking?
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
panic(ErrorGasOverflow{descriptor})
}
}
// RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
// gas consumed, the function will panic.
//
// Use case: This functionality enables refunding gas to the trasaction or block gas pools so that
// EVM-compatible chains can fully support the go-ethereum StateDb interface.
// See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *infiniteGasMeterWithLimit) RefundGas(amount sdk.Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{Descriptor: descriptor})
}
g.consumed -= amount
}
func (g *infiniteGasMeterWithLimit) IsPastLimit() bool {
return false
}
func (g *infiniteGasMeterWithLimit) IsOutOfGas() bool {
return false
}
func (g *infiniteGasMeterWithLimit) String() string {
return fmt.Sprintf("InfiniteGasMeter:\n consumed: %d", g.consumed)
}

View File

@ -369,7 +369,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, trace
}
leftoverGas := msg.Gas() - intrinsicGas
// access list preparaion is moved from ante handler to here, because it's needed when `ApplyMessage` is called
// access list preparation is moved from ante handler to here, because it's needed when `ApplyMessage` is called
// under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`.
if rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeForkBlock != nil); rules.IsBerlin {
stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())