package backend
import (
"encoding/json"
"fmt"
rpctypes "github.com/cerc-io/laconicd/rpc/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
)
// TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
// Get transaction by hash
transaction, err := b.GetTxByEthHash(hash)
if err != nil {
b.logger.Debug("tx not found", "hash", hash)
return nil, err
}
// check if block number is 0
if transaction.Height == 0 {
return nil, errors.New("genesis is not traceable")
blk, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(transaction.Height))
b.logger.Debug("block not found", "height", transaction.Height)
// check tx index is not out of bound
if uint32(len(blk.Block.Txs)) < transaction.TxIndex {
b.logger.Debug("tx index out of bounds", "index", transaction.TxIndex, "hash", hash.String(), "height", blk.Block.Height)
return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
var predecessors []*evmtypes.MsgEthereumTx
for _, txBz := range blk.Block.Txs[:transaction.TxIndex] {
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
continue
for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
predecessors = append(predecessors, ethMsg)
tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[transaction.TxIndex])
// add predecessor messages in current cosmos tx
for i := 0; i < int(transaction.MsgIndex); i++ {
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
return nil, fmt.Errorf("invalid transaction type %T", tx)
traceTxRequest := evmtypes.QueryTraceTxRequest{
Msg: ethMessage,
Predecessors: predecessors,
BlockNumber: blk.Block.Height,
BlockTime: blk.Block.Time,
BlockHash: common.Bytes2Hex(blk.BlockID.Hash),
ProposerAddress: sdk.ConsAddress(blk.Block.ProposerAddress),
ChainId: b.chainID.Int64(),
if config != nil {
traceTxRequest.TraceConfig = config
// minus one to get the context of block beginning
contextHeight := transaction.Height - 1
if contextHeight < 1 {
// 0 is a special value in `ContextWithHeight`
contextHeight = 1
traceResult, err := b.queryClient.TraceTx(rpctypes.ContextWithHeight(contextHeight), &traceTxRequest)
// 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)
return decodedResult, nil
// 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 (b *Backend) TraceBlock(height rpctypes.BlockNumber,
config *evmtypes.TraceConfig,
block *tmrpctypes.ResultBlock,
) ([]*evmtypes.TxTraceResult, error) {
txs := block.Block.Txs
txsLength := len(txs)
if txsLength == 0 {
// If there are no transactions return empty array
return []*evmtypes.TxTraceResult{}, nil
txDecoder := b.clientCtx.TxConfig.TxDecoder()
var txsMessages []*evmtypes.MsgEthereumTx
for i, tx := range txs {
decodedTx, err := txDecoder(tx)
b.logger.Error("failed to decode transaction", "hash", txs[i].Hash(), "error", err.Error())
for _, msg := range decodedTx.GetMsgs() {
ethMessage, ok := msg.(*evmtypes.MsgEthereumTx)
// Just considers Ethereum transactions
txsMessages = append(txsMessages, ethMessage)
// minus one to get the context at the beginning of the block
contextHeight := height - 1
// 0 is a special value for `ContextWithHeight`.
ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight))
traceBlockRequest := &evmtypes.QueryTraceBlockRequest{
Txs: txsMessages,
TraceConfig: config,
BlockNumber: block.Block.Height,
BlockTime: block.Block.Time,
BlockHash: common.Bytes2Hex(block.BlockID.Hash),
ProposerAddress: sdk.ConsAddress(block.Block.ProposerAddress),
res, err := b.queryClient.TraceBlock(ctxWithHeight, traceBlockRequest)
decodedResults := make([]*evmtypes.TxTraceResult, txsLength)
if err := json.Unmarshal(res.Data, &decodedResults); err != nil {
return decodedResults, nil