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

fix(evm): fix gRPC-only chain-id #1510

Closed
wants to merge 2 commits into from
Closed
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
165 changes: 165 additions & 0 deletions x/evm/keeper/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package keeper

import (
"math/big"

tmtypes "github.com/tendermint/tendermint/types"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"

ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/statedb"
"github.com/evmos/ethermint/x/evm/types"
evm "github.com/evmos/ethermint/x/evm/vm"
)

// EVMConfig creates the EVMConfig based on current state
func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, chainID *big.Int) (*types.EVMConfig, error) {
chainID, err := GetChainID(ctx, chainID)
if err != nil {
return nil, err
}

params := k.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(chainID)

// get the coinbase address from the block proposer
coinbase, err := k.GetCoinbaseAddress(ctx, proposerAddress)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to obtain coinbase address")
}

baseFee := k.GetBaseFee(ctx, ethCfg)
return &types.EVMConfig{
Params: params,
ChainConfig: ethCfg,
CoinBase: coinbase,
BaseFee: baseFee,
}, nil
}

// TxConfig loads `TxConfig` from current transient storage
func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig {
return statedb.NewTxConfig(
common.BytesToHash(ctx.HeaderHash()), // BlockHash
txHash, // TxHash
uint(k.GetTxIndexTransient(ctx)), // TxIndex
uint(k.GetLogSizeTransient(ctx)), // LogIndex
)
}

// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters
// (ChainConfig and module Params). It additionally sets the validator operator address as the
// coinbase address to make it available for the COINBASE opcode, even though there is no
// beneficiary of the coinbase transaction (since we're not mining).
func (k *Keeper) NewEVM(
ctx sdk.Context,
msg core.Message,
cfg *types.EVMConfig,
tracer vm.EVMLogger,
stateDB vm.StateDB,
) evm.EVM {
blockCtx := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
GetHash: k.GetHashFn(ctx),
Coinbase: cfg.CoinBase,
GasLimit: ethermint.BlockGasLimit(ctx),
BlockNumber: big.NewInt(ctx.BlockHeight()),
Time: big.NewInt(ctx.BlockHeader().Time.Unix()),
Difficulty: big.NewInt(0), // unused. Only required in PoW context
BaseFee: cfg.BaseFee,
Random: nil, // TODO: set randomness
}

txCtx := core.NewEVMTxContext(msg)
if tracer == nil {
tracer = k.Tracer(ctx, msg, cfg.ChainConfig)
}
vmConfig := k.VMConfig(ctx, msg, cfg, tracer)
return k.evmConstructor(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, k.customPrecompiles)
}

// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the
// module parameters. The config generated uses the default JumpTable from the EVM.
func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, cfg *types.EVMConfig, tracer vm.EVMLogger) vm.Config {
noBaseFee := true
if types.IsLondon(cfg.ChainConfig, ctx.BlockHeight()) {
noBaseFee = k.feeMarketKeeper.GetParams(ctx).NoBaseFee
}

var debug bool
if _, ok := tracer.(types.NoOpTracer); !ok {
debug = true
}

return vm.Config{
Debug: debug,
Tracer: tracer,
NoBaseFee: noBaseFee,
EnablePreimageRecording: false,
JumpTable: nil, // Set to the default by the EVM interpreter
ExtraEips: cfg.Params.EIPs(),
}
}

// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
// 1. The requested height matches the current height from context (and thus same epoch number)
// 2. The requested height is from an previous height from the same chain epoch
// 3. The requested height is from a height greater than the latest one
func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
return func(height uint64) common.Hash {
h, err := ethermint.SafeInt64(height)
if err != nil {
k.Logger(ctx).Error("failed to cast height to int64", "error", err)
return common.Hash{}
}

switch {
case ctx.BlockHeight() == h:
// Case 1: The requested height matches the one from the context so we can retrieve the header
// hash directly from the context.
// Note: The headerHash is only set at begin block, it will be nil in case of a query context
headerHash := ctx.HeaderHash()
if len(headerHash) != 0 {
return common.BytesToHash(headerHash)
}

// only recompute the hash if not set (eg: checkTxState)
contextBlockHeader := ctx.BlockHeader()
header, err := tmtypes.HeaderFromProto(&contextBlockHeader)
if err != nil {
k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err)
return common.Hash{}
}

headerHash = header.Hash()
return common.BytesToHash(headerHash)

case ctx.BlockHeight() > h:
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
// current chain epoch. This only applies if the current height is greater than the requested height.
histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h)
if !found {
k.Logger(ctx).Debug("historical info not found", "height", h)
return common.Hash{}
}

header, err := tmtypes.HeaderFromProto(&histInfo.Header)
if err != nil {
k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err)
return common.Hash{}
}

return common.BytesToHash(header.Hash())
default:
// Case 3: heights greater than the current one returns an empty hash.
return common.Hash{}
}
}
}
43 changes: 23 additions & 20 deletions x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,12 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
chainID, err := getChainID(ctx, req.ChainId)

chainID, err := GetChainID(ctx, big.NewInt(req.ChainId))
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

cfg, err := k.EVMConfig(ctx, GetProposerAddress(ctx, req.ProposerAddress), chainID)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
Expand Down Expand Up @@ -258,7 +260,7 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
}

ctx := sdk.UnwrapSDKContext(c)
chainID, err := getChainID(ctx, req.ChainId)
chainID, err := GetChainID(ctx, big.NewInt(req.ChainId))
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
Expand All @@ -268,6 +270,7 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
}

var args types.TransactionArgs

err = json.Unmarshal(req.Args, &args)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
Expand Down Expand Up @@ -394,18 +397,21 @@ func (k Keeper) TraceTx(c context.Context, req *types.QueryTraceTxRequest) (*typ
contextHeight = 1
}

ctx := sdk.UnwrapSDKContext(c)
ctx = ctx.WithBlockHeight(contextHeight)
ctx = ctx.WithBlockTime(req.BlockTime)
ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash))
chainID, err := getChainID(ctx, req.ChainId)
ctx := sdk.UnwrapSDKContext(c).
WithBlockHeight(contextHeight).
WithBlockTime(req.BlockTime).
WithHeaderHash(common.Hex2Bytes(req.BlockHash))

chainID, err := GetChainID(ctx, big.NewInt(req.ChainId))
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

cfg, err := k.EVMConfig(ctx, GetProposerAddress(ctx, req.ProposerAddress), chainID)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to load evm config: %s", err.Error())
}

signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))

txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))
Expand All @@ -417,10 +423,12 @@ func (k Keeper) TraceTx(c context.Context, req *types.QueryTraceTxRequest) (*typ
}
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i)

rsp, err := k.ApplyMessageWithConfig(ctx, msg, types.NewNoOpTracer(), true, cfg, txConfig)
if err != nil {
continue
}

txConfig.LogIndex += uint(len(rsp.Logs))
}

Expand Down Expand Up @@ -471,11 +479,12 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest)
contextHeight = 1
}

ctx := sdk.UnwrapSDKContext(c)
ctx = ctx.WithBlockHeight(contextHeight)
ctx = ctx.WithBlockTime(req.BlockTime)
ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash))
chainID, err := getChainID(ctx, req.ChainId)
ctx := sdk.UnwrapSDKContext(c).
WithBlockHeight(contextHeight).
WithBlockTime(req.BlockTime).
WithHeaderHash(common.Hex2Bytes(req.BlockHash))

chainID, err := GetChainID(ctx, big.NewInt(req.ChainId))
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
Expand All @@ -489,6 +498,7 @@ func (k Keeper) TraceBlock(c context.Context, req *types.QueryTraceBlockRequest)
results := make([]*types.TxTraceResult, 0, txsLength)

txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))

for i, tx := range req.Txs {
result := types.TxTraceResult{}
ethTx := tx.AsTransaction()
Expand Down Expand Up @@ -532,6 +542,7 @@ func (k *Keeper) traceTx(
err error
timeout = defaultTraceTimeout
)

msg, err := tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
Expand Down Expand Up @@ -617,11 +628,3 @@ func (k Keeper) BaseFee(c context.Context, _ *types.QueryBaseFeeRequest) (*types

return res, nil
}

// 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
}
Loading