rpc: debug_traceBlockByNumber endpoint (#555)

* Refactor traceTx

* add getTendermintBlock on backend

* keeper concurrency

* first version

* json rpc concurrency

* rever makefile change

* remove grpc traceblock

* create internal traceBlock function

* added types to evm module

* tendermintBlockByNumber rename

* added safe message check

* remove unnecesary line

* check error

* fix lint

* fix linter

* Update ethereum/rpc/namespaces/debug/api.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* Update ethereum/rpc/namespaces/debug/api.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* Update ethereum/rpc/backend/backend.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* improve traceBlock performance

* fix linter

* add extra line on function parameters

* changed index to uint 64

* fix lint

* proto gen

* update endpoints documentation

* update changelog

* Apply suggestions from code review

* Update ethereum/rpc/namespaces/eth/filters/filters.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
crypto-facs
2021-09-15 08:46:01 +00:00
committed by GitHub
co-authored by Federico Kunze Küllmer
parent ec7d8a37fd
commit 3f1eeb30b6
11 changed files with 346 additions and 151 deletions
+34 -23
View File
@@ -41,6 +41,7 @@ type Backend interface {
BlockNumber() (hexutil.Uint64, error)
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error)
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
PendingTransactions() ([]*sdk.Tx, error)
@@ -116,6 +117,38 @@ func (e *EVMBackend) BlockNumber() (hexutil.Uint64, error) {
// GetBlockByNumber returns the block identified by number.
func (e *EVMBackend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) {
resBlock, err := e.GetTendermintBlockByNumber(blockNum)
if err != nil {
return nil, err
}
res, err := e.EthBlockFromTendermint(resBlock.Block, fullTx)
if err != nil {
e.logger.Debug("EthBlockFromTendermint failed", "height", blockNum, "error", err.Error())
return nil, err
}
return res, nil
}
// GetBlockByHash returns the block identified by hash.
func (e *EVMBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Debug("BlockByHash block not found", "hash", hash.Hex(), "error", err.Error())
return nil, err
}
if resBlock.Block == nil {
e.logger.Debug("BlockByHash block not found", "hash", hash.Hex())
return nil, nil
}
return e.EthBlockFromTendermint(resBlock.Block, fullTx)
}
// GetTendermintBlockByNumber returns a Tendermint format block by block number
func (e *EVMBackend) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error) {
height := blockNum.Int64()
currentBlockNumber, _ := e.BlockNumber()
@@ -152,29 +185,7 @@ func (e *EVMBackend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (
e.logger.Debug("GetBlockByNumber block not found", "height", height)
return nil, nil
}
res, err := e.EthBlockFromTendermint(resBlock.Block, fullTx)
if err != nil {
e.logger.Debug("EthBlockFromTendermint failed", "height", height, "error", err.Error())
}
return res, err
}
// GetBlockByHash returns the block identified by hash.
func (e *EVMBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Debug("BlockByHash block not found", "hash", hash.Hex(), "error", err.Error())
return nil, err
}
if resBlock.Block == nil {
e.logger.Debug("BlockByHash block not found", "hash", hash.Hex())
return nil, nil
}
return e.EthBlockFromTendermint(resBlock.Block, fullTx)
return resBlock, nil
}
// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a given Tendermint block and its block result.
+99 -1
View File
@@ -13,6 +13,8 @@ import (
"sync"
"time"
"github.com/tendermint/tendermint/types"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
"github.com/cosmos/cosmos-sdk/client"
@@ -89,7 +91,7 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (
traceTxRequest := evmtypes.QueryTraceTxRequest{
Msg: ethMessage,
TxIndex: transaction.Index,
TxIndex: uint64(transaction.Index),
}
if config != nil {
@@ -112,6 +114,102 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (
return decodedResult, nil
}
// TraceBlockByNumber returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (a *API) TraceBlockByNumber(height rpctypes.BlockNumber, config *evmtypes.TraceConfig) ([]*evmtypes.TxTraceResult, error) {
a.logger.Debug("debug_traceBlockByNumber", "height", height)
if height == 0 {
return nil, errors.New("genesis is not traceable")
}
// Get Tendermint Block
resBlock, err := a.backend.GetTendermintBlockByNumber(height)
if err != nil {
return nil, err
}
return a.traceBlock(height, config, resBlock.Block.Txs)
}
// traceBlock configures a new tracer according to the provided configuration, and
// executes all the transactions contained within. The return value will be one item
// per transaction, dependent on the requested tracer.
func (a API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConfig, txs types.Txs) ([]*evmtypes.TxTraceResult, error) {
txsLength := len(txs)
if txsLength == 0 {
// If there are no transactions return empty array
return []*evmtypes.TxTraceResult{}, nil
}
var (
results = make([]*evmtypes.TxTraceResult, txsLength)
wg = new(sync.WaitGroup)
jobs = make(chan *evmtypes.TxTraceTask, txsLength)
)
threads := runtime.NumCPU()
if threads > txsLength {
threads = txsLength
}
ctxWithHeight := rpctypes.ContextWithHeight(int64(height))
wg.Add(threads)
for th := 0; th < threads; th++ {
go func() {
defer wg.Done()
txDecoder := a.clientCtx.TxConfig.TxDecoder()
// Fetch and execute the next transaction trace tasks
for task := range jobs {
tx, err := txDecoder(txs[task.Index])
if err != nil {
a.logger.Error("failed to decode transaction", "hash", txs[task.Index].Hash(), "error", err.Error())
continue
}
messages := tx.GetMsgs()
if len(messages) == 0 {
continue
}
ethMessage, ok := messages[0].(*evmtypes.MsgEthereumTx)
if !ok {
// Just considers Ethereum transactions
continue
}
traceTxRequest := &evmtypes.QueryTraceTxRequest{
Msg: ethMessage,
TxIndex: uint64(task.Index),
TraceConfig: config,
}
res, err := a.queryClient.TraceTx(ctxWithHeight, traceTxRequest)
if err != nil {
results[task.Index] = &evmtypes.TxTraceResult{Error: err.Error()}
continue
}
// 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{}
if err := json.Unmarshal(res.Data, &decodedResult); err != nil {
results[task.Index] = &evmtypes.TxTraceResult{Error: err.Error()}
continue
}
results[task.Index] = &evmtypes.TxTraceResult{Result: decodedResult}
}
}()
}
for i := range txs {
jobs <- &evmtypes.TxTraceTask{Index: i}
}
close(jobs)
wg.Wait()
return results, nil
}
// 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.