package keeper
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"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/cerc-io/laconicd/types"
"github.com/cerc-io/laconicd/x/evm/statedb"
"github.com/cerc-io/laconicd/x/evm/types"
)
var _ types.QueryServer = Keeper{}
const (
defaultTraceTimeout = 5 * time.Second
// Account implements the Query/Account gRPC method
func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*types.QueryAccountResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if err := ethermint.ValidateAddress(req.Address); err != nil {
return nil, status.Error(
codes.InvalidArgument, err.Error(),
addr := common.HexToAddress(req.Address)
ctx := sdk.UnwrapSDKContext(c)
acct := k.GetAccountOrEmpty(ctx, addr)
return &types.QueryAccountResponse{
Balance: acct.Balance.String(),
CodeHash: common.BytesToHash(acct.CodeHash).Hex(),
Nonce: acct.Nonce,
}, nil
func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRequest) (*types.QueryCosmosAccountResponse, error) {
ethAddr := common.HexToAddress(req.Address)
cosmosAddr := sdk.AccAddress(ethAddr.Bytes())
account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
res := types.QueryCosmosAccountResponse{
CosmosAddress: cosmosAddr.String(),
if account != nil {
res.Sequence = account.GetSequence()
res.AccountNumber = account.GetAccountNumber()
return &res, nil
// ValidatorAccount implements the Query/Balance gRPC method
func (k Keeper) ValidatorAccount(c context.Context, req *types.QueryValidatorAccountRequest) (*types.QueryValidatorAccountResponse, error) {
consAddr, err := sdk.ConsAddressFromBech32(req.ConsAddress)
if err != nil {
validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
if !found {
return nil, fmt.Errorf("validator not found for %s", consAddr.String())
accAddr := sdk.AccAddress(validator.GetOperator())
res := types.QueryValidatorAccountResponse{
AccountAddress: accAddr.String(),
account := k.accountKeeper.GetAccount(ctx, accAddr)
// Balance implements the Query/Balance gRPC method
func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
codes.InvalidArgument,
types.ErrZeroAddress.Error(),
balanceInt := k.GetBalance(ctx, common.HexToAddress(req.Address))
return &types.QueryBalanceResponse{
Balance: balanceInt.String(),
// Storage implements the Query/Storage gRPC method
func (k Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*types.QueryStorageResponse, error) {
address := common.HexToAddress(req.Address)
key := common.HexToHash(req.Key)
state := k.GetState(ctx, address, key)
stateHex := state.Hex()
return &types.QueryStorageResponse{
Value: stateHex,
// Code implements the Query/Code gRPC method
func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) {
acct := k.GetAccountWithoutBalance(ctx, address)
var code []byte
if acct != nil && acct.IsContract() {
code = k.GetCode(ctx, common.BytesToHash(acct.CodeHash))
return &types.QueryCodeResponse{
Code: code,
// Params implements the Query/Params gRPC method
func (k Keeper) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
params := k.GetParams(ctx)
return &types.QueryParamsResponse{
Params: params,
// EthCall implements eth_call rpc api.
func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.MsgEthereumTxResponse, error) {
var args types.TransactionArgs
err := json.Unmarshal(req.Args, &args)
return nil, status.Error(codes.InvalidArgument, err.Error())
chainID, err := getChainID(ctx, req.ChainId)
cfg, err := k.EVMConfig(ctx, GetProposerAddress(ctx, req.ProposerAddress), chainID)
return nil, status.Error(codes.Internal, err.Error())
// ApplyMessageWithConfig expect correct nonce set in msg
nonce := k.GetNonce(ctx, args.GetFrom())
args.Nonce = (*hexutil.Uint64)(&nonce)
msg, err := args.ToMessage(req.GasCap, cfg.BaseFee)
txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
// pass false to not commit StateDB
res, err := k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
return res, nil
// EstimateGas implements eth_estimateGas rpc api.
func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*types.EstimateGasResponse, error) {
if req.GasCap < ethparams.TxGas {
return nil, status.Error(codes.InvalidArgument, "gas cap cannot be lower than 21,000")
err = json.Unmarshal(req.Args, &args)
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo = 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)
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 {
cap = hi
return nil, status.Error(codes.Internal, "failed to load evm config")
txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))
// convert the tx args to an ethereum message
// NOTE: the errors from the executable below should be consistent with go-ethereum,
// so we don't wrap them with the gRPC status code
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (vmError bool, rsp *types.MsgEthereumTxResponse, err error) {
// update the message with the new gas value
msg = ethtypes.NewMessage(
msg.From(),
msg.To(),
msg.Nonce(),
msg.Value(),
gas,
msg.GasPrice(),
msg.GasFeeCap(),
msg.GasTipCap(),
msg.Data(),
msg.AccessList(),
msg.IsFake(),
rsp, err = k.ApplyMessageWithConfig(ctx, msg, nil, false, cfg, txConfig)
if errors.Is(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)
return nil, err
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
failed, result, err := executable(hi)
if failed {
if result != nil && result.VmError != vm.ErrOutOfGas.Error() {
if result.VmError == vm.ErrExecutionReverted.Error() {
return nil, types.NewExecErrorWithReason(result.Ret)
return nil, errors.New(result.VmError)
// Otherwise, the specified gas cap is too low
return nil, fmt.Errorf("gas required exceeds allowance (%d)", cap)
return &types.EstimateGasResponse{Gas: hi}, nil
// TraceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will
// be tracer dependent.
func (k Keeper) TraceTx(c context.Context, req *types.QueryTraceTxRequest) (*types.QueryTraceTxResponse, error) {
if req.TraceConfig != nil && req.TraceConfig.Limit < 0 {
return nil, status.Errorf(codes.InvalidArgument, "output limit cannot be negative, got %d", req.TraceConfig.Limit)
// minus one to get the context of block beginning
contextHeight := req.BlockNumber - 1
if contextHeight < 1 {
// 0 is a special value in `ContextWithHeight`
contextHeight = 1
ctx = ctx.WithBlockHeight(contextHeight)
ctx = ctx.WithBlockTime(req.BlockTime)
ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash))
return nil, status.Errorf(codes.Internal, "failed to load evm config: %s", err.Error())
signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))
for i, tx := range req.Predecessors {
ethTx := tx.AsTransaction()
msg, err := ethTx.AsMessage(signer, cfg.BaseFee)
continue
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i)
rsp, err := k.ApplyMessageWithConfig(ctx, msg, types.NewNoOpTracer(), true, cfg, txConfig)
txConfig.LogIndex += uint(len(rsp.Logs))
tx := req.Msg.AsTransaction()
txConfig.TxHash = tx.Hash()
if len(req.Predecessors) > 0 {
txConfig.TxIndex++
var tracerConfig json.RawMessage
if req.TraceConfig != nil && req.TraceConfig.TracerJsonConfig != "" {
// ignore error. default to no traceConfig
_ = json.Unmarshal([]byte(req.TraceConfig.TracerJsonConfig), &tracerConfig)
result, _, err := k.traceTx(ctx, cfg, txConfig, signer, tx, req.TraceConfig, false, tracerConfig)
// error will be returned with detail status from traceTx
resultData, err := json.Marshal(result)
return &types.QueryTraceTxResponse{
Data: resultData,
// TraceBlock configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment for all the transactions in the queried block.
// The return value will be tracer dependent.
func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest) (*types.QueryTraceBlockResponse, error) {
txsLength := len(req.Txs)
results := make([]*types.TxTraceResult, 0, txsLength)
for i, tx := range req.Txs {
result := types.TxTraceResult{}
traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, ethTx, req.TraceConfig, true, nil)
result.Error = err.Error()
txConfig.LogIndex = logIndex
result.Result = traceResult
results = append(results, &result)
resultData, err := json.Marshal(results)
return &types.QueryTraceBlockResponse{
// traceTx do trace on one transaction, it returns a tuple: (traceResult, nextLogIndex, error).
func (k *Keeper) traceTx(
ctx sdk.Context,
cfg *types.EVMConfig,
txConfig statedb.TxConfig,
signer ethtypes.Signer,
tx *ethtypes.Transaction,
traceConfig *types.TraceConfig,
commitMessage bool,
tracerJSONConfig json.RawMessage,
) (*interface{}, uint, error) {
// Assemble the structured logger or the JavaScript tracer
tracer tracers.Tracer
overrides *ethparams.ChainConfig
err error
timeout = defaultTraceTimeout
msg, err := tx.AsMessage(signer, cfg.BaseFee)
return nil, 0, status.Error(codes.Internal, err.Error())
if traceConfig == nil {
traceConfig = &types.TraceConfig{}
if traceConfig.Overrides != nil {
overrides = traceConfig.Overrides.EthereumConfig(cfg.ChainConfig.ChainID)
logConfig := logger.Config{
EnableMemory: traceConfig.EnableMemory,
DisableStorage: traceConfig.DisableStorage,
DisableStack: traceConfig.DisableStack,
EnableReturnData: traceConfig.EnableReturnData,
Debug: traceConfig.Debug,
Limit: int(traceConfig.Limit),
Overrides: overrides,
tracer = logger.NewStructLogger(&logConfig)
tCtx := &tracers.Context{
BlockHash: txConfig.BlockHash,
TxIndex: int(txConfig.TxIndex),
TxHash: txConfig.TxHash,
if traceConfig.Tracer != "" {
if tracer, err = tracers.New(traceConfig.Tracer, tCtx, tracerJSONConfig); err != nil {
// Define a meaningful timeout of a single transaction trace
if traceConfig.Timeout != "" {
if timeout, err = time.ParseDuration(traceConfig.Timeout); err != nil {
return nil, 0, status.Errorf(codes.InvalidArgument, "timeout value: %s", err.Error())
// Handle timeouts and RPC cancellations
deadlineCtx, cancel := context.WithTimeout(ctx.Context(), timeout)
defer cancel()
go func() {
<-deadlineCtx.Done()
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
tracer.Stop(errors.New("execution timeout"))
}()
res, err := k.ApplyMessageWithConfig(ctx, msg, tracer, commitMessage, cfg, txConfig)
var result interface{}
result, err = tracer.GetResult()
return &result, txConfig.LogIndex + uint(len(res.Logs)), nil
// BaseFee implements the Query/BaseFee gRPC method
func (k Keeper) BaseFee(c context.Context, _ *types.QueryBaseFeeRequest) (*types.QueryBaseFeeResponse, error) {
ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID)
baseFee := k.GetBaseFee(ctx, ethCfg)
res := &types.QueryBaseFeeResponse{}
if baseFee != nil {
aux := sdkmath.NewIntFromBigInt(baseFee)
res.BaseFee = &aux
// getChainID parse chainID from current context if not provided
func getChainID(ctx sdk.Context, chainID int64) (*big.Int, error) {
if chainID == 0 {
return ethermint.ParseChainID(ctx.ChainID())
return big.NewInt(chainID), nil