Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1084050
add debug_traceBlock api
cloudgray Oct 8, 2025
a7bbbbd
add test cases for debug_traceCall
cloudgray Oct 8, 2025
e55cfbc
chore: update CHANGELOG.md
cloudgray Oct 8, 2025
42da1e6
add debug_traceBlock api
cloudgray Oct 8, 2025
5797d2f
feat(rpc): add debug_getRawBlock api and fix debug_traceBlock api
cloudgray Oct 9, 2025
e83ff0f
test(rpc): fix test cases for debug apis
cloudgray Oct 9, 2025
f2785a2
test(rpc): modify test case for debug_traceCall
cloudgray Oct 9, 2025
2675cda
chore: fix lint
cloudgray Oct 9, 2025
ddcb152
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 9, 2025
1128c40
Merge branch 'main' into feat(rpc)-traceCall
vladjdk Oct 9, 2025
383dc3e
fix test code
cloudgray Oct 10, 2025
d2aab70
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 10, 2025
818b9db
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 10, 2025
acaca56
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 10, 2025
9b6b01a
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 10, 2025
19b4c86
Merge branch 'main' into feat(rpc)-traceCall
vladjdk Oct 10, 2025
5e042c8
chore: modify comments
cloudgray Oct 11, 2025
594c7ea
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 11, 2025
522d94e
fix(x/vm): apply default args to TraceCall method
cloudgray Oct 11, 2025
11e5dc5
test(jsonrpc): add type comparison of debug_traceCall response from e…
cloudgray Oct 11, 2025
f58bfa1
chore: remove useless method call
cloudgray Oct 11, 2025
693a09f
Merge branch 'main' into feat(rpc)-traceCall
cloudgray Oct 13, 2025
27c6bb9
fix: non-deterministic system test result
cloudgray Oct 13, 2025
2a80dff
fix: non-deterministic system test result
cloudgray Oct 13, 2025
6efae1d
fix: non-deterministic system test result
cloudgray Oct 13, 2025
924b20c
fix: non-deterministic system test result
cloudgray Oct 13, 2025
6d504fd
fix: non-deterministic system test result
cloudgray Oct 13, 2025
472415c
fix: non-deterministic system test result
cloudgray Oct 13, 2025
5b964c8
fix: non-deterministic system test result
cloudgray Oct 13, 2025
688753a
fix: non-deterministic system test result
cloudgray Oct 13, 2025
0a54b56
fix: non-deterministic system test result
cloudgray Oct 13, 2025
7050e41
fix: return type of eth apis
cloudgray Oct 13, 2025
f37a023
fix: non-deterministic system test result
cloudgray Oct 13, 2025
3f54925
chore: non-deterministic calculation for query
cloudgray Oct 13, 2025
1e0e84c
fix: non-deterministic system test result
cloudgray Oct 13, 2025
08abcad
test: fix eth_feeHistory api test code
cloudgray Oct 13, 2025
2643c0f
test: fix eth_feeHistory api test code
cloudgray Oct 13, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- [\#684](https://github.com/cosmos/evm/pull/684) Add unit test cases for EIP-7702
- [\#685](https://github.com/cosmos/evm/pull/685) Add EIP-7702 e2e test
- [\#680](https://github.com/cosmos/evm/pull/680) Introduce a `StaticPrecompiles` builder
- [\#691](https://github.com/cosmos/evm/pull/691) Add debug_traceCall api
- [\#701](https://github.com/cosmos/evm/pull/701) Add address codec support to ERC20 IBC callbacks to handle hex addresses in addition to bech32 addresses.
- [\#702](https://github.com/cosmos/evm/pull/702) Fix mempool e2e test
- [\#704](https://github.com/cosmos/evm/pull/704) Fix EIP-7702 test cases
Expand Down
1,911 changes: 1,695 additions & 216 deletions api/cosmos/evm/vm/v1/query.pulsar.go

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions api/cosmos/evm/vm/v1/query_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions proto/cosmos/evm/vm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ service Query {
option (google.api.http).get = "/cosmos/evm/vm/v1/trace_block";
}

// TraceCall implements the `debug_traceCall` rpc api
rpc TraceCall(QueryTraceCallRequest) returns (QueryTraceCallResponse) {
option (google.api.http).get = "/cosmos/evm/vm/v1/trace_call";
}

// BaseFee queries the base fee of the parent block of the current block,
// it's similar to feemarket module's method, but also checks london hardfork
// status.
Expand Down Expand Up @@ -343,6 +348,32 @@ message QueryTraceBlockResponse {
bytes data = 1;
}

// QueryTraceCallRequest defines TraceCall request
message QueryTraceCallRequest {
// args uses the same json format as the json rpc api.
bytes args = 1;
// gas_cap defines the default gas cap to be used
uint64 gas_cap = 2;
// proposer_address of the requested block in hex format
bytes proposer_address = 3 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ConsAddress"];
// trace_config holds extra parameters to trace functions.
TraceConfig trace_config = 4;
// block_number of requested transaction
int64 block_number = 5;
// block_hash of requested transaction
string block_hash = 6;
// block_time of requested transaction
google.protobuf.Timestamp block_time = 7 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
// chain_id is the the eip155 chain id parsed from the requested block header
int64 chain_id = 8;
}

// QueryTraceCallResponse defines TraceCall response
message QueryTraceCallResponse {
// data is the response serialized in bytes
bytes data = 1;
}

// QueryBaseFeeRequest defines the request type for querying the EIP1559 base
// fee.
message QueryBaseFeeRequest {}
Expand Down
1 change: 1 addition & 0 deletions rpc/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ type EVMBackend interface {
// Tracing
TraceTransaction(hash common.Hash, config *types.TraceConfig) (interface{}, error)
TraceBlock(height types.BlockNumber, config *types.TraceConfig, block *tmrpctypes.ResultBlock) ([]*evmtypes.TxTraceResult, error)
TraceCall(args evmtypes.TransactionArgs, blockNrOrHash types.BlockNumberOrHash, config *types.TraceConfig) (interface{}, error)
}

var _ BackendI = (*Backend)(nil)
Expand Down
37 changes: 37 additions & 0 deletions rpc/backend/mocks/evm_query_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 68 additions & 1 deletion rpc/backend/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *rpctypes.TraceConfi
// minus one to get the context of block beginning
contextHeight := transaction.Height - 1
if contextHeight < 1 {
// 0 is a special value in `ContextWithHeight`
// In Ethereum, the genesis block height is 0, but in CometBFT, the genesis block height is 1.
// So here we set the minimum requested height to 1.
contextHeight = 1
}
traceResult, err := b.QueryClient.TraceTx(rpctypes.ContextWithHeight(contextHeight), &traceTxRequest)
Expand Down Expand Up @@ -229,3 +230,69 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber,

return decodedResults, nil
}

// TraceCall executes a call with the given arguments and returns the structured logs
// created during the execution of EVM. It returns them as a JSON object.
func (b *Backend) TraceCall(
args evmtypes.TransactionArgs,
blockNrOrHash rpctypes.BlockNumberOrHash,
config *rpctypes.TraceConfig,
) (interface{}, error) {
// Marshal tx args
bz, err := json.Marshal(&args)
if err != nil {
return nil, err
}

// Get block number from blockNrOrHash
blockNr, err := b.BlockNumberFromComet(blockNrOrHash)
if err != nil {
return nil, err
}

// Get the block to get necessary context
header, err := b.CometHeaderByNumber(blockNr)
if err != nil {
b.Logger.Debug("block not found", "number", blockNr)
return nil, err
}

traceCallRequest := evmtypes.QueryTraceCallRequest{
Args: bz,
GasCap: b.RPCGasCap(),
ProposerAddress: sdk.ConsAddress(header.Header.ProposerAddress),
BlockNumber: header.Header.Height,
BlockHash: common.Bytes2Hex(header.Header.Hash()),
BlockTime: header.Header.Time,
ChainId: b.EvmChainID.Int64(),
}

if config != nil {
traceCallRequest.TraceConfig = b.convertConfig(config)
}

// get the context of provided block
contextHeight := header.Header.Height
if contextHeight < 1 {
// In Ethereum, the genesis block height is 0, but in CometBFT, the genesis block height is 1.
// So here we set the minimum requested height to 1.
contextHeight = 1
}

// Use the block height as context for the query
ctxWithHeight := rpctypes.ContextWithHeight(contextHeight)
traceResult, err := b.QueryClient.TraceCall(ctxWithHeight, &traceCallRequest)
if err != nil {
return nil, err
}

// Response format is unknown due to custom tracer config param
// More information can be found here https://geth.ethereum.org/docs/dapp/tracing-filtered
var decodedResult interface{}
err = json.Unmarshal(traceResult.Data, &decodedResult)
if err != nil {
return nil, err
}

return decodedResult, nil
}
68 changes: 68 additions & 0 deletions rpc/namespaces/ethereum/debug/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package debug
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"os"
"runtime"
"runtime/debug"
Expand All @@ -14,6 +16,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
stderrors "github.com/pkg/errors"

Expand Down Expand Up @@ -102,6 +105,71 @@ func (a *API) TraceBlockByHash(hash common.Hash, config *rpctypes.TraceConfig) (
return a.backend.TraceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
}

// TraceBlock returns the structured logs created during the execution of
// EVM and returns them as a JSON object. It accepts an RLP-encoded block.
func (a *API) TraceBlock(tblockRlp hexutil.Bytes, config *rpctypes.TraceConfig) ([]*evmtypes.TxTraceResult, error) {
a.logger.Debug("debug_traceBlock", "size", len(tblockRlp))
// Decode RLP-encoded block
var block types.Block
if err := rlp.DecodeBytes(tblockRlp, &block); err != nil {
a.logger.Debug("failed to decode block", "error", err.Error())
return nil, fmt.Errorf("could not decode block: %w", err)
}

// Get block number from the decoded block
blockNum := block.NumberU64()
if blockNum > math.MaxInt64 {
return nil, fmt.Errorf("block number overflow: %d exceeds max int64", blockNum)
}
blockNumber := rpctypes.BlockNumber(blockNum) //#nosec G115 -- overflow checked above
a.logger.Debug("decoded block", "number", blockNumber, "hash", block.Hash().Hex())

// Get CometBFT block by number (not hash, as Ethereum block hash may differ from CometBFT hash)
resBlock, err := a.backend.CometBlockByNumber(blockNumber)
if err != nil {
a.logger.Debug("get block failed", "number", blockNumber, "error", err.Error())
return nil, err
}

if resBlock == nil || resBlock.Block == nil {
a.logger.Debug("block not found", "number", blockNumber)
return nil, errors.New("block not found")
}

return a.backend.TraceBlock(blockNumber, config, resBlock)
}

// TraceCall lets you trace a given eth_call. It collects the structured logs
// created during the execution of EVM if the given transaction was added on
// top of the provided block and returns them as a JSON object.
func (a *API) TraceCall(args evmtypes.TransactionArgs, blockNrOrHash rpctypes.BlockNumberOrHash, config *rpctypes.TraceConfig) (interface{}, error) {
a.logger.Debug("debug_traceCall", "args", args, "block number or hash", blockNrOrHash)
return a.backend.TraceCall(args, blockNrOrHash, config)
}

// GetRawBlock retrieves the RLP-encoded block by block number or hash.
func (a *API) GetRawBlock(blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
a.logger.Debug("debug_getRawBlock", "block number or hash", blockNrOrHash)

// Get block number from blockNrOrHash
blockNum, err := a.backend.BlockNumberFromComet(blockNrOrHash)
if err != nil {
return nil, err
}

// Get Ethereum block by number
block, err := a.backend.EthBlockByNumber(blockNum)
if err != nil {
return nil, err
}
if block == nil {
return nil, fmt.Errorf("block not found")
}

// Encode block to RLP
return rlp.EncodeToBytes(block)
}

// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
// file. It uses a profile rate of 1 for most accurate information. If a different rate is
// desired, set the rate and write the profile manually.
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/rpc/backend/test_evm_query_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ func RegisterTraceBlockError(queryClient *mocks.EVMQueryClient) {
Return(nil, errortypes.ErrInvalidRequest)
}

// TraceCall
func RegisterTraceCall(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx) {
data := []byte{0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x20, 0x22, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x22, 0x7d} // {"test": "trace_call"}
queryClient.On("TraceCall", rpc.ContextWithHeight(1), mock.AnythingOfType("*types.QueryTraceCallRequest")).
Return(&evmtypes.QueryTraceCallResponse{Data: data}, nil)
}

func RegisterTraceCallWithTracer(queryClient *mocks.EVMQueryClient, msgEthTx *evmtypes.MsgEthereumTx, tracer string) {
data := []byte{0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x43, 0x41, 0x4c, 0x4c, 0x22, 0x7d} // {"type": "CALL"}
queryClient.On("TraceCall", rpc.ContextWithHeight(1), mock.MatchedBy(func(req *evmtypes.QueryTraceCallRequest) bool {
return req.TraceConfig != nil && req.TraceConfig.Tracer == tracer
})).Return(&evmtypes.QueryTraceCallResponse{Data: data}, nil)
}

func RegisterTraceCallError(queryClient *mocks.EVMQueryClient) {
queryClient.On("TraceCall", rpc.ContextWithHeight(1), mock.AnythingOfType("*types.QueryTraceCallRequest")).
Return(nil, errortypes.ErrInvalidRequest)
}

// Params
func RegisterParams(queryClient *mocks.EVMQueryClient, header *metadata.MD, height int64) {
queryClient.On("Params", rpc.ContextWithHeight(height), &evmtypes.QueryParamsRequest{}, grpc.Header(header)).
Expand Down
Loading
Loading