grpc: add eth_call query command (#236)

* add eth_call query command

Implement EthCall grpc query api

Closes #229

add eth_call query command

fix codec issue

use query client

use grpc status error and codes

validate address length in grpc handler

* Update x/evm/types/callargs.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang
2021-07-09 09:04:46 +00:00
committed by GitHub
co-authored by Federico Kunze Küllmer
parent 7951852bb0
commit 0020e4f2cd
13 changed files with 3332 additions and 521 deletions
+15 -134
View File
@@ -3,11 +3,11 @@ package eth
import (
"bytes"
"context"
"encoding/json"
"fmt"
"math/big"
"strings"
"github.com/gogo/protobuf/jsonpb"
"github.com/pkg/errors"
"github.com/spf13/viper"
log "github.com/xlab/suplog"
@@ -20,7 +20,6 @@ import (
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
abci "github.com/tendermint/tendermint/abci/types"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/ethereum/go-ethereum/accounts/keystore"
@@ -518,19 +517,13 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
}
// Call performs a raw contract call.
func (e *PublicAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *rpctypes.StateOverride) (hexutil.Bytes, error) {
func (e *PublicAPI) Call(args evmtypes.CallArgs, blockNr rpctypes.BlockNumber, _ *rpctypes.StateOverride) (hexutil.Bytes, error) {
e.logger.Debugln("eth_call", "args", args.String(), "block number", blockNr)
simRes, err := e.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit))
data, err := e.doCall(args, blockNr)
if err != nil {
return []byte{}, err
}
data, err := evmtypes.DecodeTxResponse(simRes.Result.Data)
if err != nil {
e.logger.WithError(err).Warningln("call result decoding failed")
return []byte{}, err
}
if data.Reverted {
return []byte{}, evmtypes.NewExecErrorWithReason(data.Ret)
}
@@ -541,147 +534,35 @@ func (e *PublicAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _
// DoCall performs a simulated call operation through the evmtypes. It returns the
// estimated gas used on the operation or an error if fails.
func (e *PublicAPI) doCall(
args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, globalGasCap *big.Int,
) (*sdk.SimulationResponse, error) {
// Set default gas & gas price if none were set
// Change this to uint64(math.MaxUint64 / 2) if gas cap can be configured
gas := uint64(ethermint.DefaultRPCGasLimit)
if args.Gas != nil {
gas = uint64(*args.Gas)
args evmtypes.CallArgs, blockNr rpctypes.BlockNumber,
) (*evmtypes.MsgEthereumTxResponse, error) {
bz, err := json.Marshal(&args)
if err != nil {
return nil, err
}
if globalGasCap != nil && globalGasCap.Uint64() < gas {
e.logger.Debugln("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap.Uint64()
}
// Set gas price using default or parameter if passed in
gasPrice := new(big.Int).SetUint64(ethermint.DefaultGasPrice)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
// Set value for transaction
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
// Set Data if provided
var data []byte
if args.Data != nil {
data = []byte(*args.Data)
}
var accessList *ethtypes.AccessList
if args.AccessList != nil {
accessList = args.AccessList
}
if args.From == nil {
args.From = &common.Address{}
}
includePending := blockNr == rpctypes.EthPendingBlockNumber
seq, err := e.getAccountNonce(*args.From, includePending, 0, e.logger)
req := evmtypes.EthCallRequest{Args: bz}
res, err := e.queryClient.EthCall(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
if err != nil {
return nil, err
}
// Create new call message
msg := evmtypes.NewTx(e.chainIDEpoch, seq, args.To, value, gas, gasPrice, data, accessList)
msg.From = args.From.String()
// TODO: get from chain config
signer := ethtypes.LatestSignerForChainID(e.chainIDEpoch)
if err := msg.Sign(signer, e.clientCtx.Keyring); err != nil {
return nil, err
}
if err := msg.ValidateBasic(); err != nil {
return nil, err
}
// Create a TxBuilder
txBuilder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
log.Panicln("clientCtx.TxConfig.NewTxBuilder returns unsupported builder")
}
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
if err != nil {
log.Panicln("codectypes.NewAnyWithValue failed to pack an obvious value")
}
txBuilder.SetExtensionOptions(option)
if err := txBuilder.SetMsgs(msg); err != nil {
log.Panicln("builder.SetMsgs failed")
}
// Query params to use the EVM denomination
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
if err != nil {
e.logger.WithError(err).Errorln("failed to query evm params")
return nil, err
}
txData, err := evmtypes.UnpackTxData(msg.Data)
if err != nil {
e.logger.WithError(err).Errorln("failed to unpack tx data")
return nil, err
}
fees := sdk.Coins{sdk.NewCoin(res.Params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))}
txBuilder.SetFeeAmount(fees)
txBuilder.SetGasLimit(gas)
// doc about generate and transform tx into json, protobuf bytes
// https://github.com/cosmos/cosmos-sdk/blob/master/docs/run-node/txs.md
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return nil, err
}
// simulate by calling ABCI Query
query := abci.RequestQuery{
Path: "/app/simulate",
Data: txBytes,
Height: blockNr.Int64(),
}
queryResult, err := e.clientCtx.QueryABCI(query)
if err != nil {
return nil, err
}
var simResponse sdk.SimulationResponse
err = jsonpb.Unmarshal(strings.NewReader(string(queryResult.Value)), &simResponse)
if err != nil {
return nil, err
}
return &simResponse, nil
return res, nil
}
// EstimateGas returns an estimate of gas usage for the given smart contract call.
//EstimateGas returns an estimate of gas usage for the given smart contract call.
// It adds 1,000 gas to the returned value instead of using the gas adjustment
// param from the SDK.
func (e *PublicAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, error) {
func (e *PublicAPI) EstimateGas(args evmtypes.CallArgs) (hexutil.Uint64, error) {
e.logger.Debugln("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.
simRes, err := e.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit))
data, err := e.doCall(args, 0)
if err != nil {
return 0, err
}
data, err := evmtypes.DecodeTxResponse(simRes.Result.Data)
if err != nil {
e.logger.WithError(err).Warningln("call result decoding failed")
return 0, err
}
if data.Reverted {
return 0, evmtypes.NewExecErrorWithReason(data.Ret)
}
@@ -1111,7 +992,7 @@ func (e *PublicAPI) setTxDefaults(args rpctypes.SendTxArgs) (rpctypes.SendTxArgs
input = args.Data
}
callArgs := rpctypes.CallArgs{
callArgs := evmtypes.CallArgs{
From: &args.From, // From shouldn't be nil
To: args.To,
Gas: args.Gas,
-23
View File
@@ -127,29 +127,6 @@ func (args *SendTxArgs) ToTransaction() *evmtypes.MsgEthereumTx {
return tx
}
// CallArgs represents the arguments for a call.
type CallArgs struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data *hexutil.Bytes `json:"data"`
AccessList *ethtypes.AccessList `json:"accessList"`
}
// String return the struct in a string format
func (args *CallArgs) String() string {
// Todo: There is currently a bug with hexutil.Big when the value its nil, printing would trigger an exception
return fmt.Sprintf("SendTxArgs{From:%v, To:%v, Gas:%v,"+
" Data:%v, AccessList:%v}",
args.From,
args.To,
args.Gas,
args.Data,
args.AccessList)
}
// StateOverride is the collection of overridden accounts.
type StateOverride map[common.Address]OverrideAccount