Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

### IMPROVEMENTS

- [\#538](https://github.com/cosmos/evm/pull/538) Optimize `eth_estimateGas` gRPC path: short-circuit plain transfers, add optimistic gas bound based on `MaxUsedGas`.
- [\#513](https://github.com/cosmos/evm/pull/513) Replace `TestEncodingConfig` with production `EncodingConfig` in encoding package to remove test dependencies from production code.
- [\#467](https://github.com/cosmos/evm/pull/467) Replace GlobalEVMMempool by passing to JSONRPC on initiate.
- [\#352](https://github.com/cosmos/evm/pull/352) Remove the creation of a Geth EVM instance, stateDB during the AnteHandler balance check.
Expand Down
197 changes: 128 additions & 69 deletions api/cosmos/evm/vm/v1/tx.pulsar.go

Large diffs are not rendered by default.

78 changes: 76 additions & 2 deletions evmd/tests/integration/x_vm_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,87 @@
package integration

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/suite"

"github.com/cosmos/evm/server/config"
"github.com/cosmos/evm/tests/integration/x/vm"
"github.com/cosmos/evm/testutil/integration/evm/network"
"github.com/cosmos/evm/testutil/keyring"
feemarkettypes "github.com/cosmos/evm/x/feemarket/types"
"github.com/cosmos/evm/x/vm/types"
"github.com/ethereum/go-ethereum/common"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

func BenchmarkGasEstimation(b *testing.B) {
// Setup benchmark test environment
keys := keyring.New(2)
// Set custom balance based on test params
customGenesis := network.CustomGenesisState{}
feemarketGenesis := feemarkettypes.DefaultGenesisState()
feemarketGenesis.Params.NoBaseFee = true
customGenesis[feemarkettypes.ModuleName] = feemarketGenesis
opts := []network.ConfigOption{
network.WithPreFundedAccounts(keys.GetAllAccAddrs()...),
network.WithCustomGenesis(customGenesis),
}
nw := network.NewUnitTestNetwork(CreateEvmd, opts...)
// gh := grpc.NewIntegrationHandler(nw)
// tf := factory.New(nw, gh)

chainConfig := types.DefaultChainConfig(nw.GetEIP155ChainID().Uint64())
// get the denom and decimals set on chain initialization
// because we'll need to set them again when resetting the chain config
denom := types.GetEVMCoinDenom()
extendedDenom := types.GetEVMCoinExtendedDenom()
displayDenom := types.GetEVMCoinDisplayDenom()
decimals := types.GetEVMCoinDecimals()

configurator := types.NewEVMConfigurator()
configurator.ResetTestConfig()
err := configurator.
WithChainConfig(chainConfig).
WithEVMCoinInfo(types.EvmCoinInfo{
Denom: denom,
ExtendedDenom: extendedDenom,
DisplayDenom: displayDenom,
Decimals: decimals,
}).
Configure()
require.NoError(b, err)

// Use simple transaction args for consistent benchmarking
args := types.TransactionArgs{
To: &common.Address{},
}

marshalArgs, err := json.Marshal(args)
require.NoError(b, err)

req := types.EthCallRequest{
Args: marshalArgs,
GasCap: config.DefaultGasCap,
ProposerAddress: nw.GetContext().BlockHeader().ProposerAddress,
}

// Reset timer to exclude setup time
b.ResetTimer()

// Run the benchmark
for i := 0; i < b.N; i++ {
_, err := nw.GetEvmClient().EstimateGas(
nw.GetContext(),
&req,
)
if err != nil {
b.Fatal(err)
}
}
}

func TestKeeperTestSuite(t *testing.T) {
s := vm.NewKeeperTestSuite(CreateEvmd)
s.EnableFeemarket = false
Expand Down
2 changes: 2 additions & 0 deletions proto/cosmos/evm/vm/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ message MsgEthereumTxResponse {
string vm_error = 4;
// gas_used specifies how much gas was consumed by the transaction
uint64 gas_used = 5;
// max_used_gas specifies the gas consumed by the transaction, not including refunds
uint64 max_used_gas = 6;
}

// MsgUpdateParams defines a Msg for updating the x/vm module parameters.
Expand Down
60 changes: 48 additions & 12 deletions x/vm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,20 +404,32 @@ func (k Keeper) EstimateGasInternal(c context.Context, req *types.EthCallRequest
return len(rsp.VmError) > 0, rsp, nil
}

// Execute the binary search and hone in on an executable gas limit
hi, err = types.BinSearch(lo, hi, executable)
// Adapted from go-ethereum gas estimator for early short-circuit and optimistic bounds:
// https://github.com/ethereum/go-ethereum/blob/v1.16.2/eth/gasestimator/gasestimator.go

// If the transaction is a plain value transfer, short circuit estimation and
// directly try 21000. Returning 21000 without any execution is dangerous as
// some tx field combos might bump the price up even for plain transfers (e.g.
// unused access list items). Ever so slightly wasteful, but safer overall.
if len(msg.Data) == 0 && msg.To != nil {
acct := k.GetAccountWithoutBalance(ctx, *msg.To)
if acct == nil || !acct.IsContract() {
failed, _, err := executable(ethparams.TxGas)
if err == nil && !failed {
return &types.EstimateGasResponse{Gas: ethparams.TxGas}, nil
}
}
}

// We first execute the transaction at the highest allowable gas limit, since if this fails we
// can return error immediately.
failed, result, err := executable(hi)
if err != nil {
return nil, err
}

// Reject the transaction as invalid if it still fails at the highest allowance
if hi == gasCap {
failed, result, err := executable(hi)
if err != nil {
return nil, err
}

if failed {
if failed {
// Preserve Cosmos error semantics when the cap is reached
if hi == gasCap {
if result != nil && result.VmError != vm.ErrOutOfGas.Error() {
if result.VmError == vm.ErrExecutionReverted.Error() {
return &types.EstimateGasResponse{
Expand All @@ -427,10 +439,34 @@ func (k Keeper) EstimateGasInternal(c context.Context, req *types.EthCallRequest
}
return nil, errors.New(result.VmError)
}
// Otherwise, the specified gas cap is too low
return nil, fmt.Errorf("gas required exceeds allowance (%d)", gasCap)
}
// If no larger allowance is available, fail fast
return nil, fmt.Errorf("gas required exceeds allowance (%d)", hi)
}

// There's a fairly high chance for the transaction to execute successfully
// with gasLimit set to the first execution's usedGas + gasRefund. Explicitly
// check that gas amount and use as a limit for the binary search.
optimisticGasLimit := (result.MaxUsedGas + ethparams.CallStipend) * 64 / 63
if optimisticGasLimit < hi {
failed, _, err = executable(optimisticGasLimit)
if err != nil {
return nil, err
}
if failed {
lo = optimisticGasLimit
} else {
hi = optimisticGasLimit
}
}

// Binary search for the smallest gas limit that allows the tx to execute successfully.
hi, err = types.BinSearch(lo, hi, executable)
if err != nil {
return nil, err
}

return &types.EstimateGasResponse{Gas: hi}, nil
}

Expand Down
17 changes: 9 additions & 8 deletions x/vm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,12 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, trace
return nil, errorsmod.Wrap(types.ErrGasOverflow, "apply message")
}
// refund gas
temporaryGasUsed := msg.GasLimit - leftoverGas
refund := GasToRefund(stateDB.GetRefund(), temporaryGasUsed, refundQuotient)
maxUsedGas := msg.GasLimit - leftoverGas
refund := GasToRefund(stateDB.GetRefund(), maxUsedGas, refundQuotient)

// update leftoverGas and temporaryGasUsed with refund amount
leftoverGas += refund
temporaryGasUsed -= refund
temporaryGasUsed := maxUsedGas - refund

// EVM execution error needs to be available for the JSON-RPC client
var vmError string
Expand Down Expand Up @@ -508,11 +508,12 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, trace
}

return &types.MsgEthereumTxResponse{
GasUsed: gasUsed.TruncateInt().Uint64(),
VmError: vmError,
Ret: ret,
Logs: types.NewLogsFromEth(stateDB.Logs()),
Hash: txConfig.TxHash.Hex(),
GasUsed: gasUsed.TruncateInt().Uint64(),
MaxUsedGas: maxUsedGas,
VmError: vmError,
Ret: ret,
Logs: types.NewLogsFromEth(stateDB.Logs()),
Hash: txConfig.TxHash.Hex(),
}, nil
}

Expand Down
118 changes: 74 additions & 44 deletions x/vm/types/tx.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading