rpc, evm: use binary search to estimate gas (#272)

* do binary search to estimate gas

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

* extract BinSearch function and add unit test

* do estimateGas in grpc query

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang 2021-07-19 23:19:23 +08:00 committed by GitHub
parent 072f0534db
commit 14b38af8bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 831 additions and 85 deletions

View File

@ -68,6 +68,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (rpc) [#124](https://github.com/tharsis/ethermint/issues/124) Implement `txpool_content`, `txpool_inspect` and `txpool_status` RPC methods
* (rpc) [tharsis#112](https://github.com/tharsis/ethermint/pull/153) Fix `eth_coinbase` to return the ethereum address of the validator
* (rpc) [tharsis#176](https://github.com/tharsis/ethermint/issues/176) Support fetching pending nonce
* (rpc) [tharsis#272](https://github.com/tharsis/ethermint/pull/272) do binary search to estimate gas accurately
### Bug Fixes

File diff suppressed because one or more lines are too long

View File

@ -1433,6 +1433,225 @@ paths:
type: string
tags:
- Query
/ethermint/evm/v1alpha1/estimate_gas:
get:
summary: EstimateGas implements the `eth_estimateGas` rpc api
operationId: EstimateGas
responses:
'200':
description: A successful response.
schema:
type: object
properties:
gas:
type: string
format: uint64
title: the estimated gas
title: EstimateGasResponse defines EstimateGas response
default:
description: An unexpected error response
schema:
type: object
properties:
error:
type: string
code:
type: integer
format: int32
message:
type: string
details:
type: array
items:
type: object
properties:
type_url:
type: string
description: >-
A URL/resource name that uniquely identifies the type of
the serialized
protocol buffer message. This string must contain at
least
one "/" character. The last segment of the URL's path
must represent
the fully qualified name of the type (as in
`path/google.protobuf.Duration`). The name should be in
a canonical form
(e.g., leading "." is not accepted).
In practice, teams usually precompile into the binary
all types that they
expect it to use in the context of Any. However, for
URLs which use the
scheme `http`, `https`, or no scheme, one can optionally
set up a type
server that maps type URLs to message definitions as
follows:
* If no scheme is provided, `https` is assumed.
* An HTTP GET on the URL must yield a
[google.protobuf.Type][]
value in binary format, or produce an error.
* Applications are allowed to cache lookup results based
on the
URL, or have them precompiled into a binary to avoid any
lookup. Therefore, binary compatibility needs to be preserved
on changes to types. (Use versioned type names to manage
breaking changes.)
Note: this functionality is not currently available in
the official
protobuf release, and it is not used for type URLs
beginning with
type.googleapis.com.
Schemes other than `http`, `https` (or the empty scheme)
might be
used with implementation specific semantics.
value:
type: string
format: byte
description: >-
Must be a valid serialized protocol buffer of the above
specified type.
description: >-
`Any` contains an arbitrary serialized protocol buffer
message along with a
URL that describes the type of the serialized message.
Protobuf library provides support to pack/unpack Any values
in the form
of utility functions or additional generated methods of the
Any type.
Example 1: Pack and unpack a message in C++.
Foo foo = ...;
Any any;
any.PackFrom(foo);
...
if (any.UnpackTo(&foo)) {
...
}
Example 2: Pack and unpack a message in Java.
Foo foo = ...;
Any any = Any.pack(foo);
...
if (any.is(Foo.class)) {
foo = any.unpack(Foo.class);
}
Example 3: Pack and unpack a message in Python.
foo = Foo(...)
any = Any()
any.Pack(foo)
...
if any.Is(Foo.DESCRIPTOR):
any.Unpack(foo)
...
Example 4: Pack and unpack a message in Go
foo := &pb.Foo{...}
any, err := ptypes.MarshalAny(foo)
...
foo := &pb.Foo{}
if err := ptypes.UnmarshalAny(any, foo); err != nil {
...
}
The pack methods provided by protobuf library will by
default use
'type.googleapis.com/full.type.name' as the type URL and the
unpack
methods only use the fully qualified type name after the
last '/'
in the type URL, for example "foo.bar.com/x/y.z" will yield
type
name "y.z".
JSON
====
The JSON representation of an `Any` value uses the regular
representation of the deserialized, embedded message, with
an
additional field `@type` which contains the type URL.
Example:
package google.profile;
message Person {
string first_name = 1;
string last_name = 2;
}
{
"@type": "type.googleapis.com/google.profile.Person",
"firstName": <string>,
"lastName": <string>
}
If the embedded message type is well-known and has a custom
JSON
representation, that representation will be embedded adding
a field
`value` which holds the custom JSON in addition to the
`@type`
field. Example (for message [google.protobuf.Duration][]):
{
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "1.212s"
}
parameters:
- name: args
description: same json format as the json rpc api.
in: query
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/eth_call:
get:
summary: EthCall implements the `eth_call` rpc api
@ -1721,6 +1940,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:
@ -12878,6 +13103,14 @@ definitions:
values
instead of *big.Int.
ethermint.evm.v1alpha1.EstimateGasResponse:
type: object
properties:
gas:
type: string
format: uint64
title: the estimated gas
title: EstimateGasResponse defines EstimateGas response
ethermint.evm.v1alpha1.Log:
type: object
properties:

View File

@ -32,6 +32,7 @@
- [Msg](#ethermint.evm.v1alpha1.Msg)
- [ethermint/evm/v1alpha1/query.proto](#ethermint/evm/v1alpha1/query.proto)
- [EstimateGasResponse](#ethermint.evm.v1alpha1.EstimateGasResponse)
- [EthCallRequest](#ethermint.evm.v1alpha1.EthCallRequest)
- [QueryAccountRequest](#ethermint.evm.v1alpha1.QueryAccountRequest)
- [QueryAccountResponse](#ethermint.evm.v1alpha1.QueryAccountResponse)
@ -467,6 +468,21 @@ Msg defines the evm Msg service.
<a name="ethermint.evm.v1alpha1.EstimateGasResponse"></a>
### EstimateGasResponse
EstimateGasResponse defines EstimateGas response
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `gas` | [uint64](#uint64) | | the estimated gas |
<a name="ethermint.evm.v1alpha1.EthCallRequest"></a>
### EthCallRequest
@ -476,6 +492,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 |
@ -841,6 +858,7 @@ Query defines the gRPC querier service.
| `Params` | [QueryParamsRequest](#ethermint.evm.v1alpha1.QueryParamsRequest) | [QueryParamsResponse](#ethermint.evm.v1alpha1.QueryParamsResponse) | Params queries the parameters of x/evm module. | GET|/ethermint/evm/v1alpha1/params|
| `StaticCall` | [QueryStaticCallRequest](#ethermint.evm.v1alpha1.QueryStaticCallRequest) | [QueryStaticCallResponse](#ethermint.evm.v1alpha1.QueryStaticCallResponse) | StaticCall queries the static call value of x/evm module. | GET|/ethermint/evm/v1alpha1/static_call|
| `EthCall` | [EthCallRequest](#ethermint.evm.v1alpha1.EthCallRequest) | [MsgEthereumTxResponse](#ethermint.evm.v1alpha1.MsgEthereumTxResponse) | EthCall implements the `eth_call` rpc api | GET|/ethermint/evm/v1alpha1/eth_call|
| `EstimateGas` | [EthCallRequest](#ethermint.evm.v1alpha1.EthCallRequest) | [EstimateGasResponse](#ethermint.evm.v1alpha1.EstimateGasResponse) | EstimateGas implements the `eth_estimateGas` rpc api | GET|/ethermint/evm/v1alpha1/estimate_gas|
<!-- end services -->

View File

@ -550,11 +550,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 res.Failed() {
if res.VmError == vm.ErrExecutionReverted.Error() {
@ -567,18 +577,28 @@ 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")
blockNr := rpctypes.EthPendingBlockNumber
if blockNrOptional != nil {
blockNr = *blockNrOptional
}
bz, err := json.Marshal(&args)
if err != nil {
return 0, err
}
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.
data, err := e.doCall(args, 0)
res, err := e.queryClient.EstimateGas(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
if err != nil {
return 0, err
}
return hexutil.Uint64(data.GasUsed), nil
return hexutil.Uint64(res.Gas), nil
}
// GetBlockByHash returns the block identified by hash.
@ -1027,7 +1047,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
}

View File

@ -71,6 +71,12 @@ service Query {
rpc EthCall(EthCallRequest) returns (MsgEthereumTxResponse) {
option (google.api.http).get = "/ethermint/evm/v1alpha1/eth_call";
}
// EstimateGas implements the `eth_estimateGas` rpc api
rpc EstimateGas(EthCallRequest) returns (EstimateGasResponse) {
option (google.api.http).get = "/ethermint/evm/v1alpha1/estimate_gas";
}
}
// QueryAccountRequest is the request type for the Query/Account RPC method.
@ -251,4 +257,12 @@ message QueryStaticCallResponse {
message EthCallRequest {
// same json format as the json rpc api.
bytes args = 1;
// the default gas cap to be used
uint64 gas_cap = 2;
}
// EstimateGasResponse defines EstimateGas response
message EstimateGasResponse {
// the estimated gas
uint64 gas = 1;
}

View File

@ -241,7 +241,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
if config.EVMRPC.Enable {
tmEndpoint := "/websocket"
tmRPCAddr := cfg.RPC.ListenAddress
logger.Info("EVM RPC Connecting to Tendermint WebSocket at", tmRPCAddr+tmEndpoint)
logger.Info("EVM RPC Connecting to Tendermint WebSocket at", "address", tmRPCAddr+tmEndpoint)
tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint)
rpcServer := ethrpc.NewServer()

View File

@ -803,9 +803,10 @@ func TestEth_EstimateGas(t *testing.T) {
param[0]["from"] = "0x" + fmt.Sprintf("%x", from)
param[0]["to"] = "0x1122334455667788990011223344556677889900"
param[0]["value"] = "0x1"
param[0]["gas"] = "0x5209"
rpcRes := call(t, "eth_estimateGas", param)
require.NotNil(t, rpcRes)
require.NotEmpty(t, rpcRes.Result)
require.Equal(t, rpcRes.Result, "0x5208")
var gas string
err := json.Unmarshal(rpcRes.Result, &gas)

View File

@ -3,7 +3,10 @@ package keeper
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/palantir/stacktrace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -13,7 +16,11 @@ import (
"github.com/cosmos/cosmos-sdk/types/query"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/types"
@ -374,7 +381,7 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
return nil, status.Error(codes.InvalidArgument, err.Error())
}
msg := args.ToMessage(uint64(ethermint.DefaultRPCGasLimit))
msg := args.ToMessage(req.GasCap)
params := k.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID)
@ -393,3 +400,100 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
return res, nil
}
// EstimateGas implements eth_estimateGas rpc api.
func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*types.EstimateGasResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
k.WithContext(ctx)
var args types.CallArgs
err := json.Unmarshal(req.Args, &args)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo uint64 = ethparams.TxGas - 1
hi uint64
cap uint64
)
// 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 := ctx.ConsensusParams()
if params != nil && params.Block != nil && params.Block.MaxGas > 0 {
hi = uint64(params.Block.MaxGas)
} else {
hi = req.GasCap
}
}
// TODO Recap the highest gas limit with account's available balance.
// Recap the highest gas allowance with specified gascap.
if req.GasCap != 0 && hi > req.GasCap {
hi = req.GasCap
}
cap = hi
params := k.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID)
coinbase, err := k.GetCoinbaseAddress()
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (bool, *types.MsgEthereumTxResponse, error) {
args.Gas = (*hexutil.Uint64)(&gas)
// Execute the call in an isolated context
sandboxCtx, _ := ctx.CacheContext()
k.WithContext(sandboxCtx)
msg := args.ToMessage(req.GasCap)
evm := k.NewEVM(msg, ethCfg, params, coinbase)
// pass true means execute in query mode, which don't do actual gas refund.
rsp, err := k.ApplyMessage(evm, msg, ethCfg, true)
k.WithContext(ctx)
if err != nil {
if errors.Is(stacktrace.RootCause(err), core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
}
return true, nil, err // Bail out
}
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)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// 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 nil, status.Error(codes.Internal, err.Error())
}
if failed {
if result != nil && result.VmError != vm.ErrOutOfGas.Error() {
if result.VmError == vm.ErrExecutionReverted.Error() {
return nil, status.Error(codes.Internal, types.NewExecErrorWithReason(result.Ret).Error())
}
return nil, status.Error(codes.Internal, result.VmError)
}
// Otherwise, the specified gas cap is too low
return nil, status.Error(codes.Internal, fmt.Sprintf("gas required exceeds allowance (%d)", cap))
}
}
return &types.EstimateGasResponse{Gas: hi}, nil
}

View File

@ -246,7 +246,7 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo
// Should check again even if it is checked on Ante Handler, because eth_call don't go through Ante Handler.
if msg.Gas() < intrinsicGas {
// eth_estimateGas will check for this exact error
return nil, stacktrace.Propagate(core.ErrIntrinsicGas, "intrinsic gas too low")
return nil, stacktrace.Propagate(core.ErrIntrinsicGas, "apply message")
}
leftoverGas := msg.Gas() - intrinsicGas

View File

@ -1051,6 +1051,8 @@ func (m *QueryStaticCallResponse) GetData() []byte {
type EthCallRequest struct {
// same json format as the json rpc api.
Args []byte `protobuf:"bytes,1,opt,name=args,proto3" json:"args,omitempty"`
// the default gas cap to be used
GasCap uint64 `protobuf:"varint,2,opt,name=gas_cap,json=gasCap,proto3" json:"gas_cap,omitempty"`
}
func (m *EthCallRequest) Reset() { *m = EthCallRequest{} }
@ -1093,6 +1095,59 @@ func (m *EthCallRequest) GetArgs() []byte {
return nil
}
func (m *EthCallRequest) GetGasCap() uint64 {
if m != nil {
return m.GasCap
}
return 0
}
// EstimateGasResponse defines EstimateGas response
type EstimateGasResponse struct {
// the estimated gas
Gas uint64 `protobuf:"varint,1,opt,name=gas,proto3" json:"gas,omitempty"`
}
func (m *EstimateGasResponse) Reset() { *m = EstimateGasResponse{} }
func (m *EstimateGasResponse) String() string { return proto.CompactTextString(m) }
func (*EstimateGasResponse) ProtoMessage() {}
func (*EstimateGasResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_8bbc79ec2b6c5cb2, []int{23}
}
func (m *EstimateGasResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *EstimateGasResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_EstimateGasResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *EstimateGasResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_EstimateGasResponse.Merge(m, src)
}
func (m *EstimateGasResponse) XXX_Size() int {
return m.Size()
}
func (m *EstimateGasResponse) XXX_DiscardUnknown() {
xxx_messageInfo_EstimateGasResponse.DiscardUnknown(m)
}
var xxx_messageInfo_EstimateGasResponse proto.InternalMessageInfo
func (m *EstimateGasResponse) GetGas() uint64 {
if m != nil {
return m.Gas
}
return 0
}
func init() {
proto.RegisterType((*QueryAccountRequest)(nil), "ethermint.evm.v1alpha1.QueryAccountRequest")
proto.RegisterType((*QueryAccountResponse)(nil), "ethermint.evm.v1alpha1.QueryAccountResponse")
@ -1117,6 +1172,7 @@ func init() {
proto.RegisterType((*QueryStaticCallRequest)(nil), "ethermint.evm.v1alpha1.QueryStaticCallRequest")
proto.RegisterType((*QueryStaticCallResponse)(nil), "ethermint.evm.v1alpha1.QueryStaticCallResponse")
proto.RegisterType((*EthCallRequest)(nil), "ethermint.evm.v1alpha1.EthCallRequest")
proto.RegisterType((*EstimateGasResponse)(nil), "ethermint.evm.v1alpha1.EstimateGasResponse")
}
func init() {
@ -1124,81 +1180,85 @@ func init() {
}
var fileDescriptor_8bbc79ec2b6c5cb2 = []byte{
// 1179 bytes of a gzipped FileDescriptorProto
// 1245 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x97, 0x4d, 0x6f, 0x1b, 0x45,
0x18, 0xc7, 0xbd, 0x89, 0x13, 0x27, 0x4f, 0x5e, 0x08, 0x83, 0x29, 0x61, 0x5b, 0x9c, 0x30, 0xd0,
0xc4, 0x79, 0xe9, 0x6e, 0x6d, 0xde, 0x4a, 0x85, 0x04, 0x49, 0xd5, 0x50, 0xa9, 0x05, 0x15, 0x27,
0xe2, 0xc0, 0x25, 0x1a, 0xaf, 0x57, 0x6b, 0x2b, 0xf6, 0x8e, 0xbb, 0x33, 0xb6, 0x12, 0x45, 0xb9,
0x70, 0x40, 0xbc, 0x1d, 0x40, 0x1c, 0x40, 0x48, 0x48, 0xbd, 0x72, 0xe3, 0x2b, 0x70, 0xeb, 0xb1,
0x12, 0x17, 0x4e, 0x08, 0x25, 0x1c, 0xe0, 0x5b, 0xa0, 0x79, 0x59, 0x7b, 0xd7, 0xc9, 0x7a, 0x1d,
0xc4, 0x6d, 0x66, 0xf6, 0x79, 0xf9, 0x3d, 0xcf, 0x3c, 0x9e, 0x7f, 0x02, 0xd8, 0xe5, 0x75, 0x37,
0x68, 0x35, 0x7c, 0x6e, 0xbb, 0xdd, 0x96, 0xdd, 0x2d, 0x91, 0x66, 0xbb, 0x4e, 0x4a, 0xf6, 0xa3,
0x8e, 0x1b, 0x1c, 0x59, 0xed, 0x80, 0x72, 0x8a, 0xae, 0xf4, 0x6c, 0x2c, 0xb7, 0xdb, 0xb2, 0x42,
0x1b, 0x33, 0xef, 0x51, 0x8f, 0x4a, 0x13, 0x5b, 0xac, 0x94, 0xb5, 0xb9, 0xee, 0x50, 0xd6, 0xa2,
0xcc, 0xae, 0x12, 0xe6, 0xaa, 0x30, 0x76, 0xb7, 0x54, 0x75, 0x39, 0x29, 0xd9, 0x6d, 0xe2, 0x35,
0x7c, 0xc2, 0x1b, 0xd4, 0xd7, 0xb6, 0xd7, 0x3c, 0x4a, 0xbd, 0xa6, 0x6b, 0x93, 0x76, 0xc3, 0x26,
0xbe, 0x4f, 0xb9, 0xfc, 0xc8, 0xf4, 0xd7, 0xe5, 0x04, 0x36, 0x01, 0xa1, 0x2c, 0x96, 0x12, 0x2c,
0xf8, 0xa1, 0x32, 0xc0, 0x6f, 0xc3, 0x73, 0x1f, 0x09, 0x84, 0x2d, 0xc7, 0xa1, 0x1d, 0x9f, 0x57,
0xdc, 0x47, 0x1d, 0x97, 0x71, 0xb4, 0x08, 0x39, 0x52, 0xab, 0x05, 0x2e, 0x63, 0x8b, 0xc6, 0xb2,
0x51, 0x9c, 0xae, 0x84, 0xdb, 0xdb, 0x53, 0x9f, 0x3f, 0x5e, 0xca, 0xfc, 0xfd, 0x78, 0x29, 0x83,
0x1d, 0xc8, 0xc7, 0x5d, 0x59, 0x9b, 0xfa, 0xcc, 0x15, 0xbe, 0x55, 0xd2, 0x24, 0xbe, 0xe3, 0x86,
0xbe, 0x7a, 0x8b, 0xae, 0xc2, 0xb4, 0x43, 0x6b, 0xee, 0x7e, 0x9d, 0xb0, 0xfa, 0xe2, 0x98, 0xfc,
0x36, 0x25, 0x0e, 0xee, 0x11, 0x56, 0x47, 0x79, 0x98, 0xf0, 0xa9, 0x70, 0x1a, 0x5f, 0x36, 0x8a,
0xd9, 0x8a, 0xda, 0xe0, 0x77, 0xe1, 0x45, 0x99, 0xe4, 0x8e, 0xec, 0xd9, 0x7f, 0xa0, 0xfc, 0xcc,
0x00, 0xf3, 0xa2, 0x08, 0x1a, 0xf6, 0x3a, 0xcc, 0xab, 0xeb, 0xd8, 0x8f, 0x47, 0x9a, 0x53, 0xa7,
0x5b, 0xea, 0x10, 0x99, 0x30, 0xc5, 0x44, 0x52, 0xc1, 0x37, 0x26, 0xf9, 0x7a, 0x7b, 0x11, 0x82,
0xa8, 0xa8, 0xfb, 0x7e, 0xa7, 0x55, 0x75, 0x03, 0x5d, 0xc1, 0x9c, 0x3e, 0xfd, 0x50, 0x1e, 0xe2,
0xfb, 0x70, 0x4d, 0x72, 0x7c, 0x4c, 0x9a, 0x8d, 0x1a, 0xe1, 0x34, 0x18, 0x28, 0xe6, 0x65, 0x98,
0x75, 0xa8, 0x3f, 0xc8, 0x31, 0x23, 0xce, 0xb6, 0xce, 0x55, 0xf5, 0x95, 0x01, 0x2f, 0x25, 0x44,
0xd3, 0x85, 0xad, 0xc2, 0x33, 0x21, 0x55, 0x3c, 0x62, 0x08, 0xfb, 0x3f, 0x96, 0x16, 0x0e, 0xd1,
0xb6, 0xba, 0xe7, 0xcb, 0x5c, 0xcf, 0x4d, 0x3d, 0x44, 0x3d, 0xd7, 0xb4, 0x21, 0xc2, 0xf7, 0x75,
0xb2, 0x5d, 0x4e, 0x03, 0xe2, 0xa5, 0x27, 0x43, 0x0b, 0x30, 0x7e, 0xe0, 0x1e, 0xe9, 0x79, 0x13,
0xcb, 0x48, 0xfa, 0x4d, 0x9d, 0xbe, 0x17, 0x4c, 0xa7, 0xcf, 0xc3, 0x44, 0x97, 0x34, 0x3b, 0x61,
0x72, 0xb5, 0xc1, 0x6f, 0xc2, 0x82, 0x1e, 0xa5, 0xda, 0xa5, 0x8a, 0x5c, 0x85, 0x67, 0x23, 0x7e,
0x3a, 0x05, 0x82, 0xac, 0x98, 0x7d, 0xe9, 0x35, 0x5b, 0x91, 0x6b, 0x5c, 0x06, 0x24, 0x0d, 0xf7,
0x0e, 0x1f, 0x50, 0x8f, 0x85, 0x29, 0x10, 0x64, 0xe5, 0x2f, 0x46, 0xc5, 0x97, 0xeb, 0x48, 0xf0,
0x1d, 0xdd, 0x8f, 0xd0, 0x47, 0x87, 0xb7, 0x21, 0xdb, 0xa4, 0x9e, 0x80, 0x1a, 0x2f, 0xce, 0x94,
0xaf, 0x5a, 0x17, 0x3f, 0x51, 0xd6, 0x03, 0xea, 0x55, 0xa4, 0x21, 0x3e, 0x81, 0xe7, 0xd5, 0x4d,
0x34, 0xa9, 0x73, 0x90, 0x92, 0x1e, 0xed, 0x00, 0xf4, 0xdf, 0x2a, 0xd9, 0xda, 0x99, 0xf2, 0x8a,
0xa5, 0x7e, 0x33, 0x96, 0x78, 0xd8, 0x2c, 0xf5, 0x3e, 0xea, 0x87, 0xcd, 0x7a, 0xd8, 0xbf, 0xa9,
0x4a, 0xc4, 0x33, 0x52, 0xc6, 0xcf, 0x06, 0x5c, 0x19, 0xcc, 0xaf, 0x4b, 0xd9, 0x81, 0x1c, 0x3f,
0xdc, 0x8f, 0x54, 0xb3, 0x9a, 0x54, 0xcd, 0x5e, 0x40, 0x7c, 0x46, 0x1c, 0x11, 0x5a, 0x44, 0xd8,
0xce, 0x3e, 0xf9, 0x63, 0x29, 0x53, 0x99, 0xe4, 0xb2, 0x35, 0xe8, 0xfd, 0x0b, 0xa0, 0x57, 0x53,
0xa1, 0x15, 0x44, 0x94, 0x1a, 0x2f, 0x46, 0x51, 0xb7, 0x9b, 0x94, 0xb6, 0x74, 0x6d, 0xd8, 0x86,
0x17, 0xce, 0x7d, 0xe9, 0x8f, 0x54, 0x55, 0x1c, 0xe8, 0x0b, 0x57, 0x1b, 0x9c, 0xd7, 0x37, 0xfe,
0x90, 0x04, 0xa4, 0x15, 0xb6, 0x1c, 0xef, 0xea, 0x3b, 0x0d, 0x4f, 0x75, 0x88, 0x77, 0x60, 0xb2,
0x2d, 0x4f, 0x64, 0x8c, 0x99, 0x72, 0x21, 0xa9, 0x0f, 0xca, 0x2f, 0x2c, 0x5f, 0xf9, 0xe0, 0x7b,
0x9a, 0x7a, 0x57, 0x88, 0x88, 0x73, 0x87, 0x34, 0x9b, 0xe9, 0xbf, 0x9d, 0x3c, 0x4c, 0x34, 0xfc,
0x76, 0x87, 0xcb, 0x6e, 0xcd, 0x56, 0xd4, 0x06, 0xdf, 0xd0, 0x55, 0x46, 0x23, 0xf5, 0xa7, 0xba,
0x46, 0x38, 0x09, 0xa7, 0x5a, 0xac, 0xf1, 0xab, 0x30, 0x7f, 0x97, 0xd7, 0xa3, 0x09, 0x11, 0x64,
0x49, 0xe0, 0xb1, 0xd0, 0x4a, 0xac, 0xcb, 0xff, 0xcc, 0xc3, 0x84, 0x8c, 0x8a, 0xbe, 0x37, 0x20,
0xa7, 0x9f, 0x33, 0xb4, 0x91, 0x54, 0xe2, 0x05, 0xaa, 0x65, 0x6e, 0x8e, 0x66, 0xac, 0x50, 0x71,
0xe9, 0xd3, 0xdf, 0xfe, 0xfa, 0x6e, 0x6c, 0x03, 0xad, 0xd9, 0x09, 0x22, 0xa9, 0x1f, 0x39, 0xfb,
0x58, 0x77, 0xe3, 0x04, 0xfd, 0x62, 0xc0, 0x5c, 0x4c, 0x47, 0x50, 0x69, 0x68, 0xca, 0x8b, 0x54,
0xcb, 0x2c, 0x5f, 0xc6, 0x45, 0xb3, 0xde, 0x92, 0xac, 0x65, 0x74, 0x33, 0x89, 0x35, 0x14, 0xb1,
0x73, 0xc8, 0xbf, 0x1a, 0xb0, 0x30, 0x28, 0x12, 0xe8, 0xf5, 0xa1, 0x08, 0x09, 0x0a, 0x65, 0xbe,
0x71, 0x49, 0x2f, 0xcd, 0xfe, 0x9e, 0x64, 0xbf, 0x8d, 0x6e, 0x25, 0xb1, 0x77, 0x43, 0xcf, 0x3e,
0x7e, 0x54, 0x09, 0x4f, 0xd0, 0x0f, 0x06, 0xe4, 0xb4, 0x40, 0xa4, 0x0c, 0x44, 0x5c, 0x81, 0x52,
0x06, 0x62, 0x40, 0x73, 0x70, 0x59, 0x82, 0x6e, 0xa2, 0xf5, 0x24, 0x50, 0x2d, 0x41, 0x2c, 0xd2,
0xde, 0x9f, 0x0c, 0xc8, 0x69, 0xf1, 0x48, 0x41, 0x8b, 0xeb, 0x55, 0x0a, 0xda, 0x80, 0x1e, 0xe1,
0xb7, 0x24, 0x5a, 0x09, 0xd9, 0x49, 0x68, 0x4c, 0x39, 0xf4, 0xc9, 0xec, 0xe3, 0x03, 0xf7, 0xe8,
0x04, 0x7d, 0x6d, 0x40, 0x56, 0xc8, 0x0e, 0x2a, 0xa6, 0x4c, 0x5d, 0x4f, 0xd1, 0xcc, 0xb5, 0x11,
0x2c, 0x35, 0x96, 0x2d, 0xb1, 0xd6, 0xd0, 0x6a, 0xf2, 0x58, 0xd6, 0x62, 0xed, 0xfa, 0xd6, 0x80,
0x49, 0x25, 0x54, 0x68, 0x7d, 0x68, 0x9a, 0x98, 0x02, 0x9a, 0x1b, 0x23, 0xd9, 0x6a, 0x28, 0x4b,
0x42, 0x15, 0xd1, 0x8a, 0x9d, 0xf8, 0xc7, 0xaf, 0x14, 0x13, 0xfb, 0x58, 0x48, 0x99, 0xbc, 0xc2,
0xe9, 0x9e, 0xe8, 0xa0, 0x1b, 0xc3, 0x47, 0x66, 0x40, 0x1c, 0x4d, 0x6b, 0x54, 0xf3, 0x51, 0x1f,
0x9d, 0xaa, 0x70, 0x89, 0xf1, 0xfd, 0x68, 0x00, 0xf4, 0xf5, 0x04, 0x8d, 0x90, 0x31, 0x2a, 0x49,
0xa6, 0x3d, 0xb2, 0xbd, 0x46, 0xdc, 0x90, 0x88, 0xd7, 0xd1, 0x2b, 0xc3, 0x11, 0xa5, 0x7e, 0xa1,
0x2f, 0x0c, 0x98, 0x54, 0x6a, 0x93, 0x72, 0xa1, 0x31, 0x81, 0x4b, 0xb9, 0xd0, 0xb8, 0xec, 0xe1,
0x15, 0x09, 0xb4, 0x8c, 0x0a, 0x49, 0x40, 0x4a, 0xe0, 0x64, 0xa3, 0xfa, 0x92, 0x94, 0xd2, 0xa8,
0x73, 0x2a, 0x98, 0xd2, 0xa8, 0xf3, 0x5a, 0x97, 0xde, 0x28, 0x26, 0x7d, 0xf6, 0x1d, 0x41, 0xf3,
0xa5, 0x01, 0x39, 0xad, 0x82, 0x68, 0x25, 0x29, 0x53, 0x5c, 0x26, 0xcd, 0xc4, 0x59, 0xfc, 0x80,
0x79, 0x77, 0xc5, 0x17, 0xb7, 0xd3, 0xda, 0x3b, 0xec, 0xf1, 0x14, 0x25, 0x0f, 0x46, 0xcb, 0x49,
0x3c, 0x2e, 0xaf, 0x4b, 0x98, 0xed, 0xed, 0x27, 0xa7, 0x05, 0xe3, 0xe9, 0x69, 0xc1, 0xf8, 0xf3,
0xb4, 0x60, 0x7c, 0x73, 0x56, 0xc8, 0x3c, 0x3d, 0x2b, 0x64, 0x7e, 0x3f, 0x2b, 0x64, 0x3e, 0x29,
0x7a, 0x0d, 0x5e, 0xef, 0x54, 0x2d, 0x87, 0xb6, 0x6c, 0x5e, 0x27, 0x01, 0x6b, 0xb0, 0x48, 0xb4,
0x43, 0x19, 0x8f, 0x1f, 0xb5, 0x5d, 0x56, 0x9d, 0x94, 0xff, 0x40, 0xbe, 0xf6, 0x6f, 0x00, 0x00,
0x00, 0xff, 0xff, 0xb7, 0x54, 0x23, 0x90, 0x21, 0x0f, 0x00, 0x00,
0x18, 0xc7, 0xbd, 0x8d, 0x63, 0x37, 0x4f, 0x92, 0x36, 0x4c, 0x4d, 0x1b, 0xb6, 0xc5, 0x09, 0x03,
0x8d, 0x9d, 0x97, 0xee, 0xd6, 0xe6, 0xad, 0x54, 0x20, 0x48, 0xa2, 0xa4, 0x95, 0x5a, 0x50, 0x71,
0x22, 0x0e, 0x5c, 0xac, 0xf1, 0x7a, 0xb5, 0xb6, 0x62, 0xef, 0xb8, 0x3b, 0x6b, 0x2b, 0x51, 0x94,
0x0b, 0x07, 0xc4, 0xdb, 0xa1, 0x88, 0x03, 0x08, 0x09, 0xa9, 0x57, 0x6e, 0x7c, 0x05, 0x6e, 0x3d,
0x56, 0xe2, 0xc2, 0x09, 0xa1, 0x84, 0x03, 0x1f, 0x82, 0x03, 0x9a, 0x97, 0xb5, 0x77, 0x1d, 0x6f,
0xd6, 0x41, 0xdc, 0x66, 0x66, 0x9f, 0x97, 0xdf, 0xf3, 0xcc, 0xe3, 0xf9, 0x27, 0x80, 0x6d, 0xbf,
0x61, 0x7b, 0xed, 0xa6, 0xeb, 0x9b, 0x76, 0xaf, 0x6d, 0xf6, 0x4a, 0xa4, 0xd5, 0x69, 0x90, 0x92,
0xf9, 0xb8, 0x6b, 0x7b, 0x07, 0x46, 0xc7, 0xa3, 0x3e, 0x45, 0x57, 0xfb, 0x36, 0x86, 0xdd, 0x6b,
0x1b, 0x81, 0x8d, 0x9e, 0x73, 0xa8, 0x43, 0x85, 0x89, 0xc9, 0x57, 0xd2, 0x5a, 0x5f, 0xb1, 0x28,
0x6b, 0x53, 0x66, 0xd6, 0x08, 0xb3, 0x65, 0x18, 0xb3, 0x57, 0xaa, 0xd9, 0x3e, 0x29, 0x99, 0x1d,
0xe2, 0x34, 0x5d, 0xe2, 0x37, 0xa9, 0xab, 0x6c, 0x6f, 0x38, 0x94, 0x3a, 0x2d, 0xdb, 0x24, 0x9d,
0xa6, 0x49, 0x5c, 0x97, 0xfa, 0xe2, 0x23, 0x53, 0x5f, 0x17, 0x63, 0xd8, 0x38, 0x84, 0xb4, 0x58,
0x88, 0xb1, 0xf0, 0xf7, 0xa5, 0x01, 0x7e, 0x07, 0xae, 0x7c, 0xcc, 0x11, 0xd6, 0x2d, 0x8b, 0x76,
0x5d, 0xbf, 0x62, 0x3f, 0xee, 0xda, 0xcc, 0x47, 0xf3, 0x90, 0x25, 0xf5, 0xba, 0x67, 0x33, 0x36,
0xaf, 0x2d, 0x6a, 0xc5, 0xa9, 0x4a, 0xb0, 0xbd, 0x7b, 0xf1, 0x8b, 0xa7, 0x0b, 0xa9, 0xbf, 0x9f,
0x2e, 0xa4, 0xb0, 0x05, 0xb9, 0xa8, 0x2b, 0xeb, 0x50, 0x97, 0xd9, 0xdc, 0xb7, 0x46, 0x5a, 0xc4,
0xb5, 0xec, 0xc0, 0x57, 0x6d, 0xd1, 0x75, 0x98, 0xb2, 0x68, 0xdd, 0xae, 0x36, 0x08, 0x6b, 0xcc,
0x5f, 0x10, 0xdf, 0x2e, 0xf2, 0x83, 0xfb, 0x84, 0x35, 0x50, 0x0e, 0x26, 0x5d, 0xca, 0x9d, 0x26,
0x16, 0xb5, 0x62, 0xba, 0x22, 0x37, 0xf8, 0x7d, 0x78, 0x49, 0x24, 0xd9, 0x14, 0x3d, 0xfb, 0x0f,
0x94, 0x9f, 0x6b, 0xa0, 0x8f, 0x8a, 0xa0, 0x60, 0x6f, 0xc2, 0x25, 0x79, 0x1d, 0xd5, 0x68, 0xa4,
0x59, 0x79, 0xba, 0x2e, 0x0f, 0x91, 0x0e, 0x17, 0x19, 0x4f, 0xca, 0xf9, 0x2e, 0x08, 0xbe, 0xfe,
0x9e, 0x87, 0x20, 0x32, 0x6a, 0xd5, 0xed, 0xb6, 0x6b, 0xb6, 0xa7, 0x2a, 0x98, 0x55, 0xa7, 0x1f,
0x89, 0x43, 0xfc, 0x00, 0x6e, 0x08, 0x8e, 0x4f, 0x48, 0xab, 0x59, 0x27, 0x3e, 0xf5, 0x86, 0x8a,
0x79, 0x05, 0x66, 0x2c, 0xea, 0x0e, 0x73, 0x4c, 0xf3, 0xb3, 0xf5, 0x53, 0x55, 0x7d, 0xad, 0xc1,
0xcb, 0x31, 0xd1, 0x54, 0x61, 0x05, 0xb8, 0x1c, 0x50, 0x45, 0x23, 0x06, 0xb0, 0xff, 0x63, 0x69,
0xc1, 0x10, 0x6d, 0xc8, 0x7b, 0x3e, 0xcf, 0xf5, 0xdc, 0x56, 0x43, 0xd4, 0x77, 0x4d, 0x1a, 0x22,
0xfc, 0x40, 0x25, 0xdb, 0xf1, 0xa9, 0x47, 0x9c, 0xe4, 0x64, 0x68, 0x0e, 0x26, 0xf6, 0xec, 0x03,
0x35, 0x6f, 0x7c, 0x19, 0x4a, 0xbf, 0xa6, 0xd2, 0xf7, 0x83, 0xa9, 0xf4, 0x39, 0x98, 0xec, 0x91,
0x56, 0x37, 0x48, 0x2e, 0x37, 0xf8, 0x2d, 0x98, 0x53, 0xa3, 0x54, 0x3f, 0x57, 0x91, 0x05, 0x78,
0x21, 0xe4, 0xa7, 0x52, 0x20, 0x48, 0xf3, 0xd9, 0x17, 0x5e, 0x33, 0x15, 0xb1, 0xc6, 0x65, 0x40,
0xc2, 0x70, 0x77, 0xff, 0x21, 0x75, 0x58, 0x90, 0x02, 0x41, 0x5a, 0xfc, 0x62, 0x64, 0x7c, 0xb1,
0x0e, 0x05, 0xdf, 0x56, 0xfd, 0x08, 0x7c, 0x54, 0x78, 0x13, 0xd2, 0x2d, 0xea, 0x70, 0xa8, 0x89,
0xe2, 0x74, 0xf9, 0xba, 0x31, 0xfa, 0x89, 0x32, 0x1e, 0x52, 0xa7, 0x22, 0x0c, 0xf1, 0x11, 0xbc,
0x28, 0x6f, 0xa2, 0x45, 0xad, 0xbd, 0x84, 0xf4, 0x68, 0x1b, 0x60, 0xf0, 0x56, 0x89, 0xd6, 0x4e,
0x97, 0x97, 0x0c, 0xf9, 0x9b, 0x31, 0xf8, 0xc3, 0x66, 0xc8, 0xf7, 0x51, 0x3d, 0x6c, 0xc6, 0xa3,
0xc1, 0x4d, 0x55, 0x42, 0x9e, 0xa1, 0x32, 0x7e, 0xd6, 0xe0, 0xea, 0x70, 0x7e, 0x55, 0xca, 0x36,
0x64, 0xfd, 0xfd, 0x6a, 0xa8, 0x9a, 0x42, 0x5c, 0x35, 0xbb, 0x1e, 0x71, 0x19, 0xb1, 0x78, 0x68,
0x1e, 0x61, 0x23, 0xfd, 0xec, 0x8f, 0x85, 0x54, 0x25, 0xe3, 0x8b, 0xd6, 0xa0, 0x7b, 0x23, 0xa0,
0x0b, 0x89, 0xd0, 0x12, 0x22, 0x4c, 0x8d, 0xe7, 0xc3, 0xa8, 0x1b, 0x2d, 0x4a, 0xdb, 0xaa, 0x36,
0x6c, 0xc2, 0xb5, 0x53, 0x5f, 0x06, 0x23, 0x55, 0xe3, 0x07, 0xea, 0xc2, 0xe5, 0x06, 0xe7, 0xd4,
0x8d, 0x3f, 0x22, 0x1e, 0x69, 0x07, 0x2d, 0xc7, 0x3b, 0xea, 0x4e, 0x83, 0x53, 0x15, 0xe2, 0x5d,
0xc8, 0x74, 0xc4, 0x89, 0x88, 0x31, 0x5d, 0xce, 0xc7, 0xf5, 0x41, 0xfa, 0x05, 0xe5, 0x4b, 0x1f,
0x7c, 0x5f, 0x51, 0xef, 0x70, 0x11, 0xb1, 0x36, 0x49, 0xab, 0x95, 0xfc, 0xdb, 0xc9, 0xc1, 0x64,
0xd3, 0xed, 0x74, 0x7d, 0xd1, 0xad, 0x99, 0x8a, 0xdc, 0xe0, 0x5b, 0xaa, 0xca, 0x70, 0xa4, 0xc1,
0x54, 0xd7, 0x89, 0x4f, 0x82, 0xa9, 0xe6, 0x6b, 0xfc, 0x1e, 0x5c, 0xda, 0xf2, 0x1b, 0xe1, 0x84,
0x08, 0xd2, 0xc4, 0x73, 0x58, 0x60, 0xc5, 0xd7, 0xe8, 0x1a, 0x64, 0x1d, 0xc2, 0xaa, 0x16, 0xe9,
0xa8, 0x67, 0x28, 0xe3, 0x10, 0xb6, 0x49, 0x3a, 0xb8, 0x00, 0x57, 0xb6, 0x98, 0xdf, 0x6c, 0x13,
0xdf, 0xbe, 0x47, 0x06, 0xcd, 0x98, 0x83, 0x09, 0x87, 0xc8, 0x10, 0xe9, 0x0a, 0x5f, 0x96, 0xff,
0xb9, 0x0c, 0x93, 0x82, 0x0b, 0x7d, 0xaf, 0x41, 0x56, 0x3d, 0x88, 0x68, 0x35, 0xae, 0x49, 0x23,
0x74, 0x4f, 0x5f, 0x1b, 0xcf, 0x58, 0x22, 0xe0, 0xd2, 0x67, 0xbf, 0xfd, 0xf5, 0xdd, 0x85, 0x55,
0xb4, 0x6c, 0xc6, 0xc8, 0xac, 0x7a, 0x26, 0xcd, 0x43, 0xd5, 0xcf, 0x23, 0xf4, 0x8b, 0x06, 0xb3,
0x11, 0x25, 0x42, 0xa5, 0x33, 0x53, 0x8e, 0xd2, 0x3d, 0xbd, 0x7c, 0x1e, 0x17, 0xc5, 0x7a, 0x47,
0xb0, 0x96, 0xd1, 0xed, 0x38, 0xd6, 0x40, 0x06, 0x4f, 0x21, 0xff, 0xaa, 0xc1, 0xdc, 0xb0, 0xcc,
0xa0, 0x37, 0xce, 0x44, 0x88, 0xd1, 0x38, 0xfd, 0xcd, 0x73, 0x7a, 0x29, 0xf6, 0x0f, 0x04, 0xfb,
0x5d, 0x74, 0x27, 0x8e, 0xbd, 0x17, 0x78, 0x0e, 0xf0, 0xc3, 0x5a, 0x7a, 0x84, 0x7e, 0xd0, 0x20,
0xab, 0x24, 0x26, 0x61, 0x20, 0xa2, 0x1a, 0x96, 0x30, 0x10, 0x43, 0xaa, 0x85, 0xcb, 0x02, 0x74,
0x0d, 0xad, 0xc4, 0x81, 0x2a, 0x11, 0x63, 0xa1, 0xf6, 0xfe, 0xa4, 0x41, 0x56, 0xc9, 0x4f, 0x02,
0x5a, 0x54, 0xf1, 0x12, 0xd0, 0x86, 0x14, 0x0d, 0xbf, 0x2d, 0xd0, 0x4a, 0xc8, 0x8c, 0x43, 0x63,
0xd2, 0x61, 0x40, 0x66, 0x1e, 0xee, 0xd9, 0x07, 0x47, 0xe8, 0x1b, 0x0d, 0xd2, 0x5c, 0xb8, 0x50,
0x31, 0x61, 0xea, 0xfa, 0x9a, 0xa8, 0x2f, 0x8f, 0x61, 0xa9, 0xb0, 0x4c, 0x81, 0xb5, 0x8c, 0x0a,
0xf1, 0x63, 0x59, 0x8f, 0xb4, 0xeb, 0x5b, 0x0d, 0x32, 0x52, 0xea, 0xd0, 0xca, 0x99, 0x69, 0x22,
0x1a, 0xaa, 0xaf, 0x8e, 0x65, 0xab, 0xa0, 0x0c, 0x01, 0x55, 0x44, 0x4b, 0x66, 0xec, 0x9f, 0xcf,
0x42, 0x8e, 0xcc, 0x43, 0x2e, 0x86, 0xe2, 0x0a, 0xa7, 0xfa, 0xb2, 0x85, 0x6e, 0x9d, 0x3d, 0x32,
0x43, 0xf2, 0xaa, 0x1b, 0xe3, 0x9a, 0x8f, 0xfb, 0xe8, 0xd4, 0xb8, 0x4b, 0x84, 0xef, 0x47, 0x0d,
0x60, 0xa0, 0x48, 0x68, 0x8c, 0x8c, 0x61, 0x51, 0xd3, 0xcd, 0xb1, 0xed, 0x15, 0xe2, 0xaa, 0x40,
0xbc, 0x89, 0x5e, 0x3d, 0x1b, 0x51, 0x28, 0x20, 0xfa, 0x52, 0x83, 0x8c, 0xd4, 0xab, 0x84, 0x0b,
0x8d, 0x48, 0x64, 0xc2, 0x85, 0x46, 0x85, 0x13, 0x2f, 0x09, 0xa0, 0x45, 0x94, 0x8f, 0x03, 0x92,
0x12, 0x29, 0x1a, 0x35, 0x10, 0xb5, 0x84, 0x46, 0x9d, 0xd2, 0xd1, 0x84, 0x46, 0x9d, 0x56, 0xcb,
0xe4, 0x46, 0x31, 0xe1, 0x53, 0xb5, 0x38, 0xcd, 0x57, 0x1a, 0x64, 0x95, 0x8e, 0xa2, 0xa5, 0xb8,
0x4c, 0x51, 0xa1, 0xd5, 0x63, 0x67, 0xf1, 0x43, 0xe6, 0x6c, 0xf1, 0x2f, 0x76, 0xb7, 0xbd, 0xbb,
0xdf, 0xe7, 0x29, 0x0a, 0x1e, 0x8c, 0x16, 0xe3, 0x78, 0x6c, 0xbf, 0x21, 0x61, 0x9e, 0x68, 0x30,
0x1d, 0x52, 0xe5, 0xb1, 0x81, 0x62, 0xaf, 0x6d, 0x84, 0xc4, 0xe3, 0x35, 0x81, 0xb3, 0x84, 0x5e,
0x8b, 0xc5, 0x51, 0x4e, 0x55, 0x87, 0xb0, 0x8d, 0x8d, 0x67, 0xc7, 0x79, 0xed, 0xf9, 0x71, 0x5e,
0xfb, 0xf3, 0x38, 0xaf, 0x3d, 0x39, 0xc9, 0xa7, 0x9e, 0x9f, 0xe4, 0x53, 0xbf, 0x9f, 0xe4, 0x53,
0x9f, 0x16, 0x9d, 0xa6, 0xdf, 0xe8, 0xd6, 0x0c, 0x8b, 0xb6, 0x4d, 0xbf, 0x41, 0x3c, 0xd6, 0x64,
0xa1, 0x88, 0xfb, 0x22, 0xa6, 0x7f, 0xd0, 0xb1, 0x59, 0x2d, 0x23, 0xfe, 0x2b, 0x7e, 0xfd, 0xdf,
0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0xa4, 0x09, 0x32, 0xf6, 0x0f, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1238,6 +1298,8 @@ type QueryClient interface {
StaticCall(ctx context.Context, in *QueryStaticCallRequest, opts ...grpc.CallOption) (*QueryStaticCallResponse, error)
// EthCall implements the `eth_call` rpc api
EthCall(ctx context.Context, in *EthCallRequest, opts ...grpc.CallOption) (*MsgEthereumTxResponse, error)
// EstimateGas implements the `eth_estimateGas` rpc api
EstimateGas(ctx context.Context, in *EthCallRequest, opts ...grpc.CallOption) (*EstimateGasResponse, error)
}
type queryClient struct {
@ -1356,6 +1418,15 @@ func (c *queryClient) EthCall(ctx context.Context, in *EthCallRequest, opts ...g
return out, nil
}
func (c *queryClient) EstimateGas(ctx context.Context, in *EthCallRequest, opts ...grpc.CallOption) (*EstimateGasResponse, error) {
out := new(EstimateGasResponse)
err := c.cc.Invoke(ctx, "/ethermint.evm.v1alpha1.Query/EstimateGas", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// QueryServer is the server API for Query service.
type QueryServer interface {
// Account queries an Ethereum account.
@ -1383,6 +1454,8 @@ type QueryServer interface {
StaticCall(context.Context, *QueryStaticCallRequest) (*QueryStaticCallResponse, error)
// EthCall implements the `eth_call` rpc api
EthCall(context.Context, *EthCallRequest) (*MsgEthereumTxResponse, error)
// EstimateGas implements the `eth_estimateGas` rpc api
EstimateGas(context.Context, *EthCallRequest) (*EstimateGasResponse, error)
}
// UnimplementedQueryServer can be embedded to have forward compatible implementations.
@ -1425,6 +1498,9 @@ func (*UnimplementedQueryServer) StaticCall(ctx context.Context, req *QueryStati
func (*UnimplementedQueryServer) EthCall(ctx context.Context, req *EthCallRequest) (*MsgEthereumTxResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EthCall not implemented")
}
func (*UnimplementedQueryServer) EstimateGas(ctx context.Context, req *EthCallRequest) (*EstimateGasResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EstimateGas not implemented")
}
func RegisterQueryServer(s grpc1.Server, srv QueryServer) {
s.RegisterService(&_Query_serviceDesc, srv)
@ -1646,6 +1722,24 @@ func _Query_EthCall_Handler(srv interface{}, ctx context.Context, dec func(inter
return interceptor(ctx, in, info, handler)
}
func _Query_EstimateGas_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EthCallRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(QueryServer).EstimateGas(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ethermint.evm.v1alpha1.Query/EstimateGas",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(QueryServer).EstimateGas(ctx, req.(*EthCallRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Query_serviceDesc = grpc.ServiceDesc{
ServiceName: "ethermint.evm.v1alpha1.Query",
HandlerType: (*QueryServer)(nil),
@ -1698,6 +1792,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{
MethodName: "EthCall",
Handler: _Query_EthCall_Handler,
},
{
MethodName: "EstimateGas",
Handler: _Query_EstimateGas_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "ethermint/evm/v1alpha1/query.proto",
@ -2456,6 +2554,11 @@ func (m *EthCallRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.GasCap != 0 {
i = encodeVarintQuery(dAtA, i, uint64(m.GasCap))
i--
dAtA[i] = 0x10
}
if len(m.Args) > 0 {
i -= len(m.Args)
copy(dAtA[i:], m.Args)
@ -2466,6 +2569,34 @@ func (m *EthCallRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *EstimateGasResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *EstimateGasResponse) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *EstimateGasResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.Gas != 0 {
i = encodeVarintQuery(dAtA, i, uint64(m.Gas))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func encodeVarintQuery(dAtA []byte, offset int, v uint64) int {
offset -= sovQuery(v)
base := offset
@ -2802,6 +2933,21 @@ func (m *EthCallRequest) Size() (n int) {
if l > 0 {
n += 1 + l + sovQuery(uint64(l))
}
if m.GasCap != 0 {
n += 1 + sovQuery(uint64(m.GasCap))
}
return n
}
func (m *EstimateGasResponse) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Gas != 0 {
n += 1 + sovQuery(uint64(m.Gas))
}
return n
}
@ -4890,6 +5036,94 @@ func (m *EthCallRequest) Unmarshal(dAtA []byte) error {
m.Args = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field GasCap", wireType)
}
m.GasCap = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.GasCap |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipQuery(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthQuery
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *EstimateGasResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: EstimateGasResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: EstimateGasResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Gas", wireType)
}
m.Gas = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Gas |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipQuery(dAtA[iNdEx:])

View File

@ -611,6 +611,42 @@ func local_request_Query_EthCall_0(ctx context.Context, marshaler runtime.Marsha
}
var (
filter_Query_EstimateGas_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Query_EstimateGas_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq EthCallRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_EstimateGas_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.EstimateGas(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Query_EstimateGas_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq EthCallRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_EstimateGas_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.EstimateGas(ctx, &protoReq)
return msg, metadata, err
}
// RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
// UnaryRPC :call QueryServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -857,6 +893,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
})
mux.Handle("GET", pattern_Query_EstimateGas_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Query_EstimateGas_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_EstimateGas_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -1138,6 +1194,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
})
mux.Handle("GET", pattern_Query_EstimateGas_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Query_EstimateGas_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_EstimateGas_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -1165,6 +1241,8 @@ var (
pattern_Query_StaticCall_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"ethermint", "evm", "v1alpha1", "static_call"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Query_EthCall_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"ethermint", "evm", "v1alpha1", "eth_call"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Query_EstimateGas_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"ethermint", "evm", "v1alpha1", "estimate_gas"}, "", runtime.AssumeColonVerbOpt(true)))
)
var (
@ -1191,4 +1269,6 @@ var (
forward_Query_StaticCall_0 = runtime.ForwardResponseMessage
forward_Query_EthCall_0 = runtime.ForwardResponseMessage
forward_Query_EstimateGas_0 = runtime.ForwardResponseMessage
)

View File

@ -69,3 +69,24 @@ func UnwrapEthereumMsg(tx *sdk.Tx) (*MsgEthereumTx, error) {
return msg, nil
}
// BinSearch execute the binary search and hone in on an executable gas limit
func BinSearch(lo uint64, hi uint64, executable func(uint64) (bool, *MsgEthereumTxResponse, error)) (uint64, error) {
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
}
}
return hi, nil
}

View File

@ -1,6 +1,7 @@
package types_test
import (
"errors"
"math/big"
"testing"
@ -79,3 +80,21 @@ func TestUnwrapEthererumMsg(t *testing.T) {
require.Nil(t, err)
require.Equal(t, msg_, msg)
}
func TestBinSearch(t *testing.T) {
success_executable := func(gas uint64) (bool, *evmtypes.MsgEthereumTxResponse, error) {
target := uint64(21000)
return gas < target, nil, nil
}
failed_executable := func(gas uint64) (bool, *evmtypes.MsgEthereumTxResponse, error) {
return true, nil, errors.New("contract failed")
}
gas, err := evmtypes.BinSearch(20000, 21001, success_executable)
require.NoError(t, err)
require.Equal(t, gas, uint64(21000))
gas, err = evmtypes.BinSearch(20000, 21001, failed_executable)
require.Error(t, err)
require.Equal(t, gas, uint64(0))
}