Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
do binary search to estimate gas
Browse files Browse the repository at this point in the history
Closes #268

- Also refactor ApplyMessage to be more reuseable

move binary search to rpc api side to have a clean context each try

remove EstimateGas grpc api
  • Loading branch information
yihuang committed Jul 13, 2021
1 parent aa31409 commit 287eeff
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 93 deletions.
2 changes: 1 addition & 1 deletion client/docs/statik/statik.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions client/docs/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1996,6 +1996,12 @@ paths:
required: false
type: string
format: byte
- name: gas_cap
description: the default gas cap to be used.
in: query
required: false
type: string
format: uint64
tags:
- Query
/ethermint/evm/v1alpha1/params:
Expand Down
1 change: 1 addition & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ EthCallRequest defines EthCall request
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `args` | [bytes](#bytes) | | same json format as the json rpc api. |
| `gas_cap` | [uint64](#uint64) | | the default gas cap to be used |



Expand Down
114 changes: 104 additions & 10 deletions ethereum/rpc/namespaces/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
ethparams "github.com/ethereum/go-ethereum/params"

"github.com/tharsis/ethermint/crypto/hd"
"github.com/tharsis/ethermint/ethereum/rpc/backend"
Expand Down Expand Up @@ -538,11 +539,21 @@ func (e *PublicAPI) doCall(
if err != nil {
return nil, err
}
req := evmtypes.EthCallRequest{Args: bz}
req := evmtypes.EthCallRequest{Args: bz, GasCap: ethermint.DefaultRPCGasLimit}

// From ContextWithHeight: if the provided height is 0,
// it will return an empty context and the gRPC query will use
// the latest block height for querying.
res, err := e.queryClient.EthCall(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
if err != nil {
return nil, err
}
if len(res.VmError) > 0 {
if res.VmError == vm.ErrExecutionReverted.Error() {
return nil, evmtypes.NewExecErrorWithReason(res.Ret)
}
return nil, errors.New(res.VmError)
}

if len(res.VmError) > 0 {
if res.VmError == vm.ErrExecutionReverted.Error() {
Expand All @@ -555,18 +566,100 @@ func (e *PublicAPI) doCall(
}

// EstimateGas returns an estimate of gas usage for the given smart contract call.
func (e *PublicAPI) EstimateGas(args evmtypes.CallArgs) (hexutil.Uint64, error) {
func (e *PublicAPI) EstimateGas(args evmtypes.CallArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
e.logger.Debug("eth_estimateGas")

// From ContextWithHeight: if the provided height is 0,
// it will return an empty context and the gRPC query will use
// the latest block height for querying.
data, err := e.doCall(args, 0)
if err != nil {
return 0, err
blockNr := rpctypes.EthPendingBlockNumber
if blockNrOptional != nil {
blockNr = *blockNrOptional
}

// Binary search the gas requirement, as it may be higher than the amount used
var (
lo uint64 = ethparams.TxGas - 1
hi uint64
cap uint64
gasCap uint64 = uint64(ethermint.DefaultRPCGasLimit)
)

// Determine the highest gas limit can be used during the estimation.
if args.Gas != nil && uint64(*args.Gas) >= ethparams.TxGas {
hi = uint64(*args.Gas)
} else {
// Query block gas limit
params, err := e.clientCtx.Client.ConsensusParams(e.ctx, blockNr.TmHeight())
if err != nil {
return 0, err
}
if params.ConsensusParams.Block.MaxGas > 0 {
hi = uint64(params.ConsensusParams.Block.MaxGas)
} else {
hi = gasCap
}
}

// TODO Recap the highest gas limit with account's available balance.

// Recap the highest gas allowance with specified gascap.
if gasCap != 0 && hi > gasCap {
hi = gasCap
}
cap = hi

// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (bool, *evmtypes.MsgEthereumTxResponse, error) {
args.Gas = (*hexutil.Uint64)(&gas)
bz, err := json.Marshal(&args)
if err != nil {
return true, nil, err
}
req := evmtypes.EthCallRequest{Args: bz, GasCap: gasCap}
rsp, err := e.queryClient.EthCall(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
if err != nil {
// FIXME only ErrIntrinsicGas need to retry
// if errors.Is(err, core.ErrIntrinsicGas) {
// return true, nil, status.Error(codes.Internal, err.Error()) // Bail out
// }
return true, nil, nil // Special case, raise gas limit
}
return len(rsp.VmError) > 0, rsp, nil
}

return hexutil.Uint64(data.GasUsed), nil
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
failed, _, err := executable(mid)

// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigned. Return the error directly, don't struggle any more.
if err != nil {
return 0, err
}
if failed {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
failed, result, err := executable(hi)
if err != nil {
return 0, err
}
if failed {
if result != nil && result.VmError != vm.ErrOutOfGas.Error() {
if result.VmError == vm.ErrExecutionReverted.Error() {
return 0, evmtypes.NewExecErrorWithReason(result.Ret)
}
return 0, errors.New(result.VmError)
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}
}
return hexutil.Uint64(hi), nil
}

// GetBlockByHash returns the block identified by hash.
Expand Down Expand Up @@ -1005,7 +1098,8 @@ func (e *PublicAPI) setTxDefaults(args rpctypes.SendTxArgs) (rpctypes.SendTxArgs
Data: input,
AccessList: args.AccessList,
}
estimated, err := e.EstimateGas(callArgs)
blockNr := rpctypes.NewBlockNumber(big.NewInt(0))
estimated, err := e.EstimateGas(callArgs, &blockNr)
if err != nil {
return args, err
}
Expand Down
16 changes: 13 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module github.com/tharsis/ethermint
go 1.15

require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/aokoli/goutils v1.1.1 // indirect
github.com/armon/go-metrics v0.3.9
github.com/aws/aws-sdk-go v1.38.21 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
Expand All @@ -15,21 +19,27 @@ require (
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/ibc-go v1.0.0-beta1
github.com/deckarep/golang-set v1.7.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.1 // indirect
github.com/ethereum/go-ethereum v1.10.3
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.3
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/improbable-eng/grpc-web v0.14.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/miguelmota/go-ethereum-hdwallet v0.0.1
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mwitkow/go-proto-validators v0.3.2 // indirect
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177
github.com/pkg/errors v0.9.1
github.com/prometheus/tsdb v0.10.0 // indirect
github.com/pseudomuto/protoc-gen-doc v1.4.1 // indirect
github.com/rakyll/statik v0.1.7
github.com/regen-network/cosmos-proto v0.3.1
github.com/rjeczalik/notify v0.9.2 // indirect
Expand All @@ -44,8 +54,8 @@ require (
github.com/tyler-smith/go-bip39 v1.1.0
github.com/xlab/closer v0.0.0-20190328110542-03326addb7c2
github.com/xlab/suplog v1.3.0
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
google.golang.org/genproto v0.0.0-20210707164411-8c882eb9abba
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a
google.golang.org/grpc v1.39.0
gopkg.in/yaml.v2 v2.4.0
nhooyr.io/websocket v1.8.7 // indirect
Expand Down
Loading

0 comments on commit 287eeff

Please sign in to comment.