diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index de8a5fd312..b5c1c18604 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -4,6 +4,7 @@ package jsonrpc import ( + "encoding/json" "errors" "fmt" "math" @@ -12,8 +13,10 @@ import ( "github.com/ethereum/go-ethereum" "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/eth/tracers" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/labstack/gommon/log" "github.com/samber/lo" @@ -662,43 +665,27 @@ func (e *EVMChain) iscRequestsInBlock(evmBlockNumber uint64) (*blocklog.BlockInf return blocklog.GetRequestsInBlock(blocklogStatePartition, iscBlockIndex) } -func (e *EVMChain) Trace(config *tracers.TraceConfig, txIndex *uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (any, error) { +func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockInfo, requestsInBlock []isc.Request, txIndex uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (json.RawMessage, error) { tracerType := "callTracer" if config.Tracer != nil { tracerType = *config.Tracer } - iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber) - if err != nil { - return nil, err - } - - var blockTxs types.Transactions - var txi int - if txIndex != nil { - txi = int(*txIndex) - } else { - blockTxs, err = e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber)) - if err != nil { - return nil, err - } - } - tracer, err := newTracer(tracerType, &tracers.Context{ BlockHash: blockHash, BlockNumber: new(big.Int).SetUint64(blockNumber), - TxIndex: txi, + TxIndex: int(txIndex), TxHash: txHash, - }, config.TracerConfig, blockTxs) + }, config.TracerConfig) if err != nil { return nil, err } err = e.backend.EVMTrace( - iscBlock.PreviousAliasOutput, - iscBlock.Timestamp, - iscRequestsInBlock, - txIndex, + blockInfo.PreviousAliasOutput, + blockInfo.Timestamp, + requestsInBlock, + &txIndex, &blockNumber, tracer, ) @@ -709,6 +696,53 @@ func (e *EVMChain) Trace(config *tracers.TraceConfig, txIndex *uint64, txHash co return tracer.GetResult() } +func (e *EVMChain) traceTransaction(config *tracers.TraceConfig, txIndex uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (any, error) { + iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber) + if err != nil { + return nil, err + } + + result, err := e.trace(config, iscBlock, iscRequestsInBlock, txIndex, txHash, blockNumber, blockHash) + if err != nil { + return nil, err + } + + return result, nil +} + +func (e *EVMChain) traceBlock(config *tracers.TraceConfig, blockNumber uint64, blockHash common.Hash) (any, error) { + iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber) + if err != nil { + return nil, err + } + + blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber)) + if err != nil { + return nil, err + } + + results := make([]TxTraceResult, 0) + + for i, tx := range blockTxs { + result, err := e.trace(config, iscBlock, iscRequestsInBlock, uint64(i), tx.Hash(), blockNumber, blockHash) + + if err == nil { + results = append(results, TxTraceResult{ + TxHash: tx.Hash(), + Result: result, + }) + } + + if err != nil && !errors.Is(err, ErrIncorrectTopLevelCalls) { + return nil, err + } + + // Continue the loop for next TXs + } + + return results, nil +} + func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceConfig) (any, error) { e.log.Debugf("TraceTransaction(txHash=%v, config=?)", txHash) @@ -717,10 +751,10 @@ func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceCon return nil, err } if blockNumber == 0 { - return nil, errors.New("tx not found") + return nil, errors.New("transaction not found") } - return e.Trace(config, &txIndex, txHash, blockNumber, blockHash) + return e.traceTransaction(config, txIndex, txHash, blockNumber, blockHash) } func (e *EVMChain) TraceBlockByHash(blockHash common.Hash, config *tracers.TraceConfig) (any, error) { @@ -728,10 +762,10 @@ func (e *EVMChain) TraceBlockByHash(blockHash common.Hash, config *tracers.Trace block := e.BlockByHash(blockHash) if block == nil { - return nil, errors.New("block not found") + return nil, fmt.Errorf("block not found: %s", blockHash.String()) } - return e.Trace(config, nil, common.Hash{}, block.Number().Uint64(), blockHash) + return e.traceBlock(config, block.Number().Uint64(), blockHash) } func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (any, error) { @@ -739,23 +773,57 @@ func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceC block, err := e.BlockByNumber(big.NewInt(int64(blockNumber))) if err != nil { - return nil, fmt.Errorf("block not found: %w", err) + return nil, fmt.Errorf("block not found: %d", blockNumber) + } + + return e.traceBlock(config, blockNumber, block.Hash()) +} + +func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + var block *types.Block + var err error + + if h, ok := blockNrOrHash.Hash(); ok { + block = e.BlockByHash(h) + } else if n, ok := blockNrOrHash.Number(); ok { + block, err = e.BlockByNumber(big.NewInt(n.Int64())) + + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("block not found: %v", blockNrOrHash.String()) } - return e.Trace(config, nil, common.Hash{}, blockNumber, block.Hash()) + return rlp.EncodeToBytes(block) } -func (e *EVMChain) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, []*types.Transaction, error) { - e.log.Debugf("GetBlockReceipts(blockNumber=%v)", blockNumber) - bn := parseBlockNumber(blockNumber) - chainState, err := e.iscStateFromEVMBlockNumber(bn) +func (e *EVMChain) GetBlockReceipts(blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, []*types.Transaction, error) { + e.log.Debugf("GetBlockReceipts(blockNumber=%v)", blockNrOrHash.String()) + + var block *types.Block + var err error + + if h, ok := blockNrOrHash.Hash(); ok { + block = e.BlockByHash(h) + } else if n, ok := blockNrOrHash.Number(); ok { + block, err = e.BlockByNumber(parseBlockNumber(n)) + + if err != nil { + return nil, nil, err + } + } else { + return nil, nil, fmt.Errorf("block not found: %v", blockNrOrHash.String()) + } + + chainState, err := e.iscStateFromEVMBlockNumberOrHash(&blockNrOrHash) if err != nil { return nil, nil, err } db := blockchainDB(chainState) - return db.GetReceiptsByBlockNumber(bn.Uint64()), db.GetTransactionsByBlockNumber(bn.Uint64()), nil + return db.GetReceiptsByBlockNumber(block.NumberU64()), db.GetTransactionsByBlockNumber(block.NumberU64()), nil } var maxUint32 = big.NewInt(math.MaxUint32) diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index 61143a374a..1daac43941 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -607,43 +607,6 @@ func TestRPCTraceTx(t *testing.T) { require.Contains(t, trace2.GasUsed.String(), "0x") } -func TestRPCTraceEvmDeposit(t *testing.T) { - env := newSoloTestEnv(t) - wallet, _ := env.solo.NewKeyPairWithFunds() - _, evmAddr := env.soloChain.NewEthereumAccountWithL2Funds() - - err := env.soloChain.TransferAllowanceTo( - isc.NewAssetsBaseTokens(1000), - isc.NewEthereumAddressAgentID(env.soloChain.ChainID, evmAddr), - wallet) - - block := env.BlockByNumber(nil) - require.NoError(t, err) - txs := block.Transactions() - tx := txs[0] - - require.Equal(t, evmAddr, *tx.To()) - - rc, err := env.TxReceipt(txs[0].Hash()) - require.NoError(t, err) - require.EqualValues(t, types.ReceiptStatusSuccessful, rc.Status) - - var res1 json.RawMessage - err = env.RawClient.CallContext( - context.Background(), - &res1, - "debug_traceTransaction", - tx.Hash().Hex(), - tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)}, - ) - require.NoError(t, err) - - var trace1 []jsonrpc.CallFrame - err = json.Unmarshal(res1, &trace1) - require.NoError(t, err) - require.Len(t, trace1, 0) -} - func TestRPCTraceBlock(t *testing.T) { env := newSoloTestEnv(t) creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() @@ -712,13 +675,17 @@ func TestRPCTraceBlock(t *testing.T) { require.Len(t, traceBlock, 2) - trace1 := traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { + var trace1 jsonrpc.CallFrame + err = json.Unmarshal(traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { return v.TxHash == tx1.Hash() - })].Result + })].Result, &trace1) + require.NoError(t, err) - trace2 := traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { + var trace2 jsonrpc.CallFrame + err = json.Unmarshal(traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { return v.TxHash == tx2.Hash() - })].Result + })].Result, &trace2) + require.NoError(t, err) require.Equal(t, creatorAddress, trace1.From) require.Equal(t, contractAddress, *trace1.To) @@ -765,6 +732,91 @@ func TestRPCTraceBlock(t *testing.T) { require.Contains(t, innerCall2.GasUsed.String(), "0x") } +func TestRPCTraceBlockSingleCall(t *testing.T) { + env := newSoloTestEnv(t) + creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() + contractABI, err := abi.JSON(strings.NewReader(evmtest.ISCTestContractABI)) + require.NoError(t, err) + _, _, contractAddress := env.DeployEVMContract(creator, contractABI, evmtest.ISCTestContractBytecode) + + // make it so that 2 requests are included in the same block + tx1 := types.MustSignNewTx(creator, types.NewEIP155Signer(big.NewInt(int64(env.ChainID))), + &types.LegacyTx{ + Nonce: env.NonceAt(creatorAddress), + To: &contractAddress, + Value: big.NewInt(123), + Gas: 100000, + GasPrice: big.NewInt(10000000000), + Data: lo.Must(contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(2))), + }) + + req1 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx1)) + env.soloChain.WaitForRequestsMark() + env.soloChain.Env.AddRequestsToMempool(env.soloChain, []isc.Request{req1}) + require.True(t, env.soloChain.WaitForRequestsThrough(1, 180*time.Second)) + + bi := env.soloChain.GetLatestBlockInfo() + require.EqualValues(t, 1, bi.NumSuccessfulRequests) + + var res1 json.RawMessage + // we have to use the raw client, because the normal client does not support debug methods + err = env.RawClient.CallContext( + context.Background(), + &res1, + "debug_traceBlockByNumber", + hexutil.Uint64(env.BlockNumber()).String(), + tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)}, + ) + require.NoError(t, err) + + var res2 json.RawMessage + // we have to use the raw client, because the normal client does not support debug methods + err = env.RawClient.CallContext( + context.Background(), + &res2, + "debug_traceBlockByHash", + env.BlockByNumber(big.NewInt(int64(env.BlockNumber()))).Hash(), + tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)}, + ) + require.NoError(t, err) + + require.Equal(t, res1, res2, "debug_traceBlockByNumber and debug_traceBlockByHash should produce equal results") + + traceBlock := make([]jsonrpc.TxTraceResult, 0) + err = json.Unmarshal(res1, &traceBlock) + require.NoError(t, err) + + require.Len(t, traceBlock, 1) + + var trace1 jsonrpc.CallFrame + err = json.Unmarshal(traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { + return v.TxHash == tx1.Hash() + })].Result, &trace1) + require.NoError(t, err) + + require.Equal(t, creatorAddress, trace1.From) + require.Equal(t, contractAddress, *trace1.To) + require.Equal(t, "0x7b", trace1.Value.String()) + expectedInput, err := contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(2)) + require.NoError(t, err) + require.Equal(t, expectedInput, []byte(trace1.Input)) + require.Empty(t, trace1.Error) + require.Empty(t, trace1.RevertReason) + require.Equal(t, "0x0", trace1.Gas.String()) + require.Equal(t, "0x0", trace1.GasUsed.String()) + + require.Len(t, trace1.Calls, 1) + innerCall1 := trace1.Calls[0] + require.Equal(t, contractAddress, innerCall1.From) + require.Equal(t, common.Address{0x1}, *innerCall1.To) + require.Equal(t, "0x2", innerCall1.Value.String()) + require.Empty(t, innerCall1.Input) + require.Empty(t, innerCall1.Error) + require.Empty(t, innerCall1.RevertReason) + require.Contains(t, innerCall1.Gas.String(), "0x") + require.Contains(t, innerCall1.GasUsed.String(), "0x") +} + func TestRPCBlockReceipt(t *testing.T) { env := newSoloTestEnv(t) creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() @@ -802,28 +854,52 @@ func TestRPCBlockReceipt(t *testing.T) { bi := env.soloChain.GetLatestBlockInfo() require.EqualValues(t, 2, bi.NumSuccessfulRequests) - var resceipts []*types.Receipt + var receipts []*types.Receipt err = env.RawClient.CallContext( context.Background(), - &resceipts, + &receipts, "eth_getBlockReceipts", hexutil.EncodeUint64(env.BlockNumber())) require.NoError(t, err) - require.Len(t, resceipts, 2) + require.Len(t, receipts, 2) - r1 := resceipts[slices.IndexFunc(resceipts, func(v *types.Receipt) bool { + r1 := receipts[slices.IndexFunc(receipts, func(v *types.Receipt) bool { return v.TxHash == tx1.Hash() })] - r2 := resceipts[slices.IndexFunc(resceipts, func(v *types.Receipt) bool { + r2 := receipts[slices.IndexFunc(receipts, func(v *types.Receipt) bool { return v.TxHash == tx2.Hash() })] require.Equal(t, uint64(1), r1.Status) require.Equal(t, big.NewInt(4), r1.BlockNumber) require.Equal(t, uint64(1), r2.Status) + require.Equal(t, big.NewInt(4), r2.BlockNumber) + + // Test the same block with its hash. + block := env.BlockByNumber(new(big.Int).SetUint64(env.BlockNumber())) + err = env.RawClient.CallContext( + context.Background(), + &receipts, + "eth_getBlockReceipts", + block.Hash().String()) + require.NoError(t, err) + + require.Len(t, receipts, 2) + + r1 = receipts[slices.IndexFunc(receipts, func(v *types.Receipt) bool { + return v.TxHash == tx1.Hash() + })] + + r2 = receipts[slices.IndexFunc(receipts, func(v *types.Receipt) bool { + return v.TxHash == tx2.Hash() + })] + + require.Equal(t, uint64(1), r1.Status) require.Equal(t, big.NewInt(4), r1.BlockNumber) + require.Equal(t, uint64(1), r2.Status) + require.Equal(t, big.NewInt(4), r2.BlockNumber) } func BenchmarkRPCEstimateGas(b *testing.B) { diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index 44624394c2..112e3e5873 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -451,14 +451,9 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip return rpcSub, nil } -func (e *EthService) GetBlockReceipts(blockNumber hexutil.Uint64) ([]map[string]interface{}, error) { +func (e *EthService) GetBlockReceipts(blockNumber rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]map[string]interface{}, error) { - feePolicy, err := e.evmChain.backend.FeePolicy(uint32(blockNumber)) - if err != nil { - return nil, err - } - - receipts, txs, err := e.evmChain.GetBlockReceipts(rpc.BlockNumber(blockNumber)) + receipts, txs, err := e.evmChain.GetBlockReceipts(blockNumber) if err != nil { return []map[string]interface{}{}, e.resolveError(err) } @@ -469,6 +464,12 @@ func (e *EthService) GetBlockReceipts(blockNumber hexutil.Uint64) ([]map[string] result := make([]map[string]interface{}, len(receipts)) for i, receipt := range receipts { + // This is pretty ugly, maybe we should shift to uint64 for internals too. + feePolicy, err := e.evmChain.backend.FeePolicy(uint32(receipt.BlockNumber.Uint64())) + if err != nil { + return nil, err + } + effectiveGasPrice := txs[i].GasPrice() if effectiveGasPrice.Sign() == 0 && !feePolicy.GasPerToken.IsEmpty() { // tx sent before gasPrice was mandatory @@ -586,6 +587,18 @@ func (d *DebugService) TraceBlockByHash(blockHash common.Hash, config *tracers.T }) } +func (d *DebugService) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (interface{}, error) { + return withMetrics(d.metrics, "debug_traceBlockByHash", func() (interface{}, error) { + return d.evmChain.GetRawBlock(blockNrOrHash) + }) +} + +func (d *DebugService) TraceBlock(blockNrOrHash rpc.BlockNumberOrHash) (interface{}, error) { + return withMetrics(d.metrics, "debug_traceBlockByHash", func() (interface{}, error) { + return d.evmChain.GetRawBlock(blockNrOrHash) + }) +} + type EVMService struct { evmChain *EVMChain } diff --git a/packages/evm/jsonrpc/tracer.go b/packages/evm/jsonrpc/tracer.go index f284911d4a..8f0a419c15 100644 --- a/packages/evm/jsonrpc/tracer.go +++ b/packages/evm/jsonrpc/tracer.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers" ) -type tracerFactory func(*tracers.Context, json.RawMessage, any) (*tracers.Tracer, error) +type tracerFactory func(*tracers.Context, json.RawMessage) (*tracers.Tracer, error) var allTracers = map[string]tracerFactory{} @@ -15,10 +15,10 @@ func registerTracer(tracerType string, fn tracerFactory) { allTracers[tracerType] = fn } -func newTracer(tracerType string, ctx *tracers.Context, cfg json.RawMessage, initValue any) (*tracers.Tracer, error) { +func newTracer(tracerType string, ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { fn := allTracers[tracerType] if fn == nil { return nil, fmt.Errorf("unsupported tracer type: %s", tracerType) } - return fn(ctx, cfg, initValue) + return fn(ctx, cfg) } diff --git a/packages/evm/jsonrpc/tracer_call.go b/packages/evm/jsonrpc/tracer_call.go index 570f8366b6..5e45c86b63 100644 --- a/packages/evm/jsonrpc/tracer_call.go +++ b/packages/evm/jsonrpc/tracer_call.go @@ -3,7 +3,6 @@ package jsonrpc import ( "encoding/json" "errors" - "fmt" "math/big" "strings" "sync/atomic" @@ -42,7 +41,7 @@ func NewOpCodeJSON(code vm.OpCode) OpCodeJSON { } func (o OpCodeJSON) MarshalJSON() ([]byte, error) { - return json.Marshal(strings.ToLower(o.String())) + return json.Marshal(strings.ToUpper(o.String())) } func (o *OpCodeJSON) UnmarshalJSON(data []byte) error { @@ -109,9 +108,9 @@ func (f *CallFrame) processOutput(output []byte, err error, reverted bool) { } type TxTraceResult struct { - TxHash common.Hash `json:"txHash"` // transaction hash - Result CallFrame `json:"result,omitempty"` // Trace results produced by the tracer - Error string `json:"error,omitempty"` // Trace failure produced by the tracer + TxHash common.Hash `json:"txHash"` // transaction hash + Result json.RawMessage `json:"result,omitempty"` // Trace results produced by the tracer + Error string `json:"error,omitempty"` // Trace failure produced by the tracer } type callTracer struct { @@ -119,9 +118,8 @@ type callTracer struct { config callTracerConfig gasLimit uint64 depth int - interrupt atomic.Bool // Atomic flag to signal execution interruption - reason error // Textual reason for the interruption - blockTxs types.Transactions // for block tracing we need this to get ordered tx hashes + interrupt atomic.Bool // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption } type callTracerConfig struct { @@ -131,17 +129,8 @@ type callTracerConfig struct { // newCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.EVMLogger. -func newCallTracer(ctx *tracers.Context, cfg json.RawMessage, initValue any) (*tracers.Tracer, error) { - var txs types.Transactions - if initValue != nil { - var ok bool - txs, ok = initValue.(types.Transactions) - if !ok { - return nil, fmt.Errorf("invalid init value type for tracer: %T", initValue) - } - } - - t, err := newCallTracerObject(ctx, cfg, txs) +func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + t, err := newCallTracerObject(ctx, cfg) if err != nil { return nil, err } @@ -158,7 +147,7 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage, initValue any) (*t }, nil } -func newCallTracerObject(_ *tracers.Context, cfg json.RawMessage, blockTxs types.Transactions) (*callTracer, error) { +func newCallTracerObject(_ *tracers.Context, cfg json.RawMessage) (*callTracer, error) { var config callTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -167,7 +156,7 @@ func newCallTracerObject(_ *tracers.Context, cfg json.RawMessage, blockTxs types } // First callframe contains tx context info // and is populated on start and end. - return &callTracer{callstack: make([]CallFrame, 0, 1), config: config, blockTxs: blockTxs}, nil + return &callTracer{callstack: make([]CallFrame, 0, 1), config: config}, nil } // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). @@ -269,32 +258,21 @@ func (t *callTracer) OnLog(log *types.Log) { t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, l) } +var ErrIncorrectTopLevelCalls = errors.New("incorrect number of top-level calls") + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *callTracer) GetResult() (json.RawMessage, error) { - if len(t.callstack) == 1 { - res, err := json.Marshal(t.callstack[0]) - if err != nil { - return nil, err - } - return res, t.reason - } - - // otherwise return all call frames - results := make([]TxTraceResult, 0, len(t.callstack)) - for i, cs := range t.callstack { - results = append(results, TxTraceResult{ - TxHash: t.blockTxs[i].Hash(), - Result: cs, - }) + if len(t.callstack) != 1 { + return nil, ErrIncorrectTopLevelCalls } - resJSON, err := json.Marshal(results) + res, err := json.Marshal(t.callstack[0]) if err != nil { return nil, err } - return resJSON, t.reason + return res, t.reason } // Stop terminates execution of the tracer at the first opportune moment.