From 83510493a28df107727de71db43c98f26b22bbe5 Mon Sep 17 00:00:00 2001 From: vitaliy Date: Mon, 9 Sep 2024 18:15:56 +0300 Subject: [PATCH 1/4] Add traceBlock endpoints --- packages/chainutil/evmtrace.go | 10 +- packages/evm/jsonrpc/chainbackend.go | 2 +- packages/evm/jsonrpc/evmchain.go | 95 +++++++++++-- packages/evm/jsonrpc/jsonrpcindex/index.go | 15 ++ .../evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 128 +++++++++++++++++- packages/evm/jsonrpc/service.go | 23 ++++ packages/evm/jsonrpc/tracer.go | 6 +- packages/evm/jsonrpc/tracer_call.go | 52 +++++-- packages/evm/jsonrpc/waspevmbackend.go | 8 +- packages/isc/sandbox_interface.go | 5 +- packages/solo/evm.go | 8 +- packages/vm/core/evm/evmimpl/internal.go | 14 +- 12 files changed, 320 insertions(+), 46 deletions(-) diff --git a/packages/chainutil/evmtrace.go b/packages/chainutil/evmtrace.go index 5b7463b602..119c810699 100644 --- a/packages/chainutil/evmtrace.go +++ b/packages/chainutil/evmtrace.go @@ -9,12 +9,13 @@ import ( "github.com/iotaledger/wasp/packages/isc" ) -func EVMTraceTransaction( +func EVMTrace( ch chain.ChainCore, aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, - txIndex uint64, + txIndex *uint64, + blockNumber *uint64, tracer *tracers.Tracer, ) error { _, err := runISCTask( @@ -24,8 +25,9 @@ func EVMTraceTransaction( iscRequestsInBlock, false, &isc.EVMTracer{ - Tracer: tracer, - TxIndex: txIndex, + Tracer: tracer, + TxIndex: txIndex, + BlockNumber: blockNumber, }, ) return err diff --git a/packages/evm/jsonrpc/chainbackend.go b/packages/evm/jsonrpc/chainbackend.go index a3dd0dbe4f..c6e9088ecb 100644 --- a/packages/evm/jsonrpc/chainbackend.go +++ b/packages/evm/jsonrpc/chainbackend.go @@ -23,7 +23,7 @@ type ChainBackend interface { EVMSendTransaction(tx *types.Transaction) error EVMCall(aliasOutput *isc.AliasOutputWithID, callMsg ethereum.CallMsg) ([]byte, error) EVMEstimateGas(aliasOutput *isc.AliasOutputWithID, callMsg ethereum.CallMsg) (uint64, error) - EVMTraceTransaction(aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, txIndex uint64, tracer *tracers.Tracer) error + EVMTrace(aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, txIndex *uint64, blockNumber *uint64, tracer *tracers.Tracer) error FeePolicy(blockIndex uint32) (*gas.FeePolicy, error) ISCChainID() *isc.ChainID ISCCallView(chainState state.State, scName string, funName string, args dict.Dict) (dict.Dict, error) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index ae2f0bd50e..7f235058af 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -425,6 +425,25 @@ func (e *EVMChain) TransactionByBlockNumberAndIndex(blockNumber *big.Int, index return txs[index], block.Hash(), bn, nil } +func (e *EVMChain) txsByBlockNumber(blockNumber *big.Int) (txs types.Transactions, err error) { + e.log.Debugf("TxsByBlockNumber(blockNumber=%v, index=%v)", blockNumber) + cachedTxs := e.index.TxsByBlockNumber(blockNumber) + if cachedTxs != nil { + return cachedTxs, nil + } + latestState, err := e.backend.ISCLatestState() + if err != nil { + return nil, err + } + db := blockchainDB(latestState) + block := db.GetBlockByNumber(blockNumber.Uint64()) + if block == nil { + return nil, err + } + + return block.Transactions(), nil +} + func (e *EVMChain) BlockByHash(hash common.Hash) *types.Block { e.log.Debugf("BlockByHash(hash=%v)", hash) @@ -639,44 +658,48 @@ func (e *EVMChain) iscRequestsInBlock(evmBlockNumber uint64) (*blocklog.BlockInf } iscBlockIndex := iscState.BlockIndex() blocklogStatePartition := subrealm.NewReadOnly(iscState, kv.Key(blocklog.Contract.Hname().Bytes())) + return blocklog.GetRequestsInBlock(blocklogStatePartition, iscBlockIndex) } -func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceConfig) (any, error) { - e.log.Debugf("TraceTransaction(txHash=%v, config=?)", txHash) +func (e *EVMChain) Trace(config *tracers.TraceConfig, txIndex *uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (any, error) { tracerType := "callTracer" if config.Tracer != nil { tracerType = *config.Tracer } - _, blockHash, blockNumber, txIndex, err := e.TransactionByHash(txHash) + iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber) if err != nil { return nil, err } - if blockNumber == 0 { - return nil, errors.New("tx not found") - } - 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: int(txIndex), + TxIndex: txi, TxHash: txHash, - }, config.TracerConfig) + }, config.TracerConfig, blockTxs) if err != nil { return nil, err } - err = e.backend.EVMTraceTransaction( + err = e.backend.EVMTrace( iscBlock.PreviousAliasOutput, iscBlock.Timestamp, iscRequestsInBlock, txIndex, + &blockNumber, tracer, ) if err != nil { @@ -686,6 +709,54 @@ func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceCon return tracer.GetResult() } +func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceConfig) (any, error) { + e.log.Debugf("TraceTransaction(txHash=%v, config=?)", txHash) + + _, blockHash, blockNumber, txIndex, err := e.TransactionByHash(txHash) + if err != nil { + return nil, err + } + if blockNumber == 0 { + return nil, errors.New("tx not found") + } + + return e.Trace(config, &txIndex, txHash, blockNumber, blockHash) +} + +func (e *EVMChain) TraceBlockByHash(blockHash common.Hash, config *tracers.TraceConfig) (any, error) { + e.log.Debugf("TraceBlockByHash(blockHash=%v, config=?)", blockHash) + + block := e.BlockByHash(blockHash) + if block == nil { + return nil, errors.New("block not found") + } + + return e.Trace(config, nil, common.Hash{}, block.Number().Uint64(), blockHash) +} + +func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (any, error) { + e.log.Debugf("TraceBlockByNumber(blockNumber=%v, config=?)", blockNumber) + + block, err := e.BlockByNumber(big.NewInt(int64(blockNumber))) + if err != nil { + return nil, fmt.Errorf("block not found: %w", err) + } + + return e.Trace(config, nil, common.Hash{}, blockNumber, block.Hash()) +} + +func (e *EVMChain) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, error) { + e.log.Debugf("GetBlockReceipts(blockNumber=%v)", blockNumber) + bn := parseBlockNumber(blockNumber) + chainState, err := e.iscStateFromEVMBlockNumber(bn) + if err != nil { + return nil, err + } + + db := blockchainDB(chainState) + return db.GetReceiptsByBlockNumber(bn.Uint64()), nil +} + var maxUint32 = big.NewInt(math.MaxUint32) // the first EVM block (number 0) is "minted" at ISC block index 0 (init chain) diff --git a/packages/evm/jsonrpc/jsonrpcindex/index.go b/packages/evm/jsonrpc/jsonrpcindex/index.go index feee3302f2..adf205101d 100644 --- a/packages/evm/jsonrpc/jsonrpcindex/index.go +++ b/packages/evm/jsonrpc/jsonrpcindex/index.go @@ -191,6 +191,21 @@ func (c *Index) TxByBlockNumberAndIndex(blockNumber *big.Int, txIndex uint64) (t return txs[txIndex], block.Hash() } +func (c *Index) TxsByBlockNumber(blockNumber *big.Int) types.Transactions { + if blockNumber == nil { + return nil + } + db := c.evmDBFromBlockIndex(uint32(blockNumber.Uint64())) + if db == nil { + return nil + } + block := db.GetBlockByNumber(blockNumber.Uint64()) + if block == nil { + return nil + } + return block.Transactions() +} + // internals const ( diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index 007e84cce4..e481cbd049 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -7,7 +7,7 @@ import ( "context" "encoding/json" "math/big" - "regexp" + "slices" "strings" "testing" "time" @@ -579,10 +579,128 @@ func TestRPCTraceTx(t *testing.T) { tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)}, ) require.NoError(t, err) - lastCallRegExp := regexp.MustCompile(`{.+"to":"0x([a-zA-Z0-9_.-]+)".*}`) - match1 := lastCallRegExp.Find(res1) - match2 := lastCallRegExp.Find(res2) - require.NotEqual(t, match1, match2) + + trace1 := jsonrpc.CallFrame{} + err = json.Unmarshal(res1, &trace1) + require.NoError(t, err) + + require.Equal(t, creatorAddress, trace1.From) + require.Equal(t, contractAddress, *trace1.To) + require.Equal(t, big.NewInt(123), trace1.Value) + expectedInput, err := contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, expectedInput, trace1.Input) + require.Empty(t, trace1.Error) + require.Empty(t, trace1.RevertReason) + + require.Len(t, trace1.Calls, 1) + trace2 := trace1.Calls[0] + require.Equal(t, contractAddress, trace2.From) + require.Equal(t, common.Address{0x1}, *trace2.To) + require.Equal(t, big.NewInt(1), trace2.Value) + require.Empty(t, trace2.Input) + require.Empty(t, trace2.Error) + require.Empty(t, trace2.RevertReason) +} + +func TestRPCTraceBlock(t *testing.T) { + env := newSoloTestEnv(t) + creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() + creator2, creatorAddress2 := 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))), + }) + + tx2 := types.MustSignNewTx(creator2, types.NewEIP155Signer(big.NewInt(int64(env.ChainID))), + &types.LegacyTx{ + Nonce: env.NonceAt(creatorAddress2), + To: &contractAddress, + Value: big.NewInt(321), + Gas: 100000, + GasPrice: big.NewInt(10000000000), + Data: lo.Must(contractABI.Pack("sendTo", common.Address{0x2}, big.NewInt(3))), + }) + + req1 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx1)) + req2 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx2)) + env.soloChain.WaitForRequestsMark() + env.soloChain.Env.AddRequestsToMempool(env.soloChain, []isc.Request{req1, req2}) + require.True(t, env.soloChain.WaitForRequestsThrough(2, 180*time.Second)) + + bi := env.soloChain.GetLatestBlockInfo() + require.EqualValues(t, 2, 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", + env.BlockNumber(), + tracers.TraceConfig{TracerConfig: []byte(`{"tracer": "callTracer"}`)}, + ) + require.NoError(t, err) + + traceBlock := make([]jsonrpc.TxTraceResult, 0) + err = json.Unmarshal(res1, &traceBlock) + require.NoError(t, err) + + require.Len(t, traceBlock, 2) + + trace1 := traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { + return v.TxHash == tx1.Hash() + })].Result + + trace2 := traceBlock[slices.IndexFunc(traceBlock, func(v jsonrpc.TxTraceResult) bool { + return v.TxHash == tx2.Hash() + })].Result + + require.Equal(t, creatorAddress, trace1.From) + require.Equal(t, contractAddress, *trace1.To) + require.Equal(t, big.NewInt(123), trace1.Value) + expectedInput, err := contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(2)) + require.NoError(t, err) + require.Equal(t, expectedInput, trace1.Input) + require.Empty(t, trace1.Error) + require.Empty(t, trace1.RevertReason) + + 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, big.NewInt(2), innerCall1.Value) + require.Empty(t, innerCall1.Input) + require.Empty(t, innerCall1.Error) + require.Empty(t, innerCall1.RevertReason) + + require.Equal(t, creatorAddress2, trace2.From) + require.Equal(t, contractAddress, *trace2.To) + require.Equal(t, big.NewInt(321), trace2.Value) + expectedInput, err = contractABI.Pack("sendTo", common.Address{0x2}, big.NewInt(3)) + require.NoError(t, err) + require.Equal(t, expectedInput, trace2.Input) + require.Empty(t, trace2.Error) + require.Empty(t, trace2.RevertReason) + + require.Len(t, trace2.Calls, 1) + innerCall2 := trace2.Calls[0] + require.Equal(t, contractAddress, innerCall2.From) + require.Equal(t, common.Address{0x2}, *innerCall2.To) + require.Equal(t, big.NewInt(3), innerCall2.Value) + require.Empty(t, innerCall2.Input) + require.Empty(t, innerCall2.Error) + require.Empty(t, innerCall2.RevertReason) } func BenchmarkRPCEstimateGas(b *testing.B) { diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index 81c5c2f808..bb71c9332a 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -451,6 +451,17 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip return rpcSub, nil } +func (e *EthService) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, error) { + return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]*types.Receipt, error) { + receipts, err := e.evmChain.GetBlockReceipts(blockNumber) + if err != nil { + return nil, e.resolveError(err) + } + + return receipts, nil + }) +} + /* Not implemented: func (e *EthService) NewFilter() @@ -543,6 +554,18 @@ func (d *DebugService) TraceTransaction(txHash common.Hash, config *tracers.Trac }) } +func (d *DebugService) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (interface{}, error) { + return withMetrics(d.metrics, "debug_traceBlockByNumber", func() (interface{}, error) { + return d.evmChain.TraceBlockByNumber(blockNumber, config) + }) +} + +func (d *DebugService) TraceBlockByHash(blockHash common.Hash, config *tracers.TraceConfig) (interface{}, error) { + return withMetrics(d.metrics, "debug_traceBlockByHash", func() (interface{}, error) { + return d.evmChain.TraceBlockByHash(blockHash, config) + }) +} + type EVMService struct { evmChain *EVMChain } diff --git a/packages/evm/jsonrpc/tracer.go b/packages/evm/jsonrpc/tracer.go index 8f0a419c15..f284911d4a 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) (*tracers.Tracer, error) +type tracerFactory func(*tracers.Context, json.RawMessage, any) (*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) (*tracers.Tracer, error) { +func newTracer(tracerType string, ctx *tracers.Context, cfg json.RawMessage, initValue any) (*tracers.Tracer, error) { fn := allTracers[tracerType] if fn == nil { return nil, fmt.Errorf("unsupported tracer type: %s", tracerType) } - return fn(ctx, cfg) + return fn(ctx, cfg, initValue) } diff --git a/packages/evm/jsonrpc/tracer_call.go b/packages/evm/jsonrpc/tracer_call.go index 346dca623b..0b3f7d4c73 100644 --- a/packages/evm/jsonrpc/tracer_call.go +++ b/packages/evm/jsonrpc/tracer_call.go @@ -3,6 +3,7 @@ package jsonrpc import ( "encoding/json" "errors" + "fmt" "math/big" "sync/atomic" @@ -85,13 +86,20 @@ 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 +} + type callTracer struct { callstack []CallFrame config callTracerConfig gasLimit uint64 depth int - interrupt atomic.Bool // Atomic flag to signal execution interruption - reason error // Textual reason for the interruption + 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 } type callTracerConfig struct { @@ -101,8 +109,18 @@ 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) (*tracers.Tracer, error) { - t, err := newCallTracerObject(ctx, cfg) +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) if err != nil { return nil, err } @@ -119,7 +137,7 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, }, nil } -func newCallTracerObject(_ *tracers.Context, cfg json.RawMessage) (*callTracer, error) { +func newCallTracerObject(_ *tracers.Context, cfg json.RawMessage, blockTxs types.Transactions) (*callTracer, error) { var config callTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -128,7 +146,7 @@ func newCallTracerObject(_ *tracers.Context, cfg json.RawMessage) (*callTracer, } // First callframe contains tx context info // and is populated on start and end. - return &callTracer{callstack: make([]CallFrame, 0, 1), config: config}, nil + return &callTracer{callstack: make([]CallFrame, 0, 1), config: config, blockTxs: blockTxs}, nil } // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). @@ -233,15 +251,29 @@ func (t *callTracer) OnLog(log *types.Log) { // 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 { - return nil, errors.New("incorrect number of top-level calls") + if len(t.callstack) == 1 { + res, err := json.Marshal(t.callstack[0]) + if err != nil { + return nil, err + } + return res, t.reason } - res, err := json.Marshal(t.callstack[0]) + // 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, + }) + } + + resJson, err := json.Marshal(results) if err != nil { return nil, err } - return res, t.reason + + return resJson, t.reason } // Stop terminates execution of the tracer at the first opportune moment. diff --git a/packages/evm/jsonrpc/waspevmbackend.go b/packages/evm/jsonrpc/waspevmbackend.go index 502faccd3b..b28d16557d 100644 --- a/packages/evm/jsonrpc/waspevmbackend.go +++ b/packages/evm/jsonrpc/waspevmbackend.go @@ -84,19 +84,21 @@ func (b *WaspEVMBackend) EVMEstimateGas(aliasOutput *isc.AliasOutputWithID, call return chainutil.EVMEstimateGas(b.chain, aliasOutput, callMsg) } -func (b *WaspEVMBackend) EVMTraceTransaction( +func (b *WaspEVMBackend) EVMTrace( aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, - txIndex uint64, + txIndex *uint64, + blockNumber *uint64, tracer *tracers.Tracer, ) error { - return chainutil.EVMTraceTransaction( + return chainutil.EVMTrace( b.chain, aliasOutput, blockTime, iscRequestsInBlock, txIndex, + blockNumber, tracer, ) } diff --git a/packages/isc/sandbox_interface.go b/packages/isc/sandbox_interface.go index 7cc4e9f673..8f3f54389e 100644 --- a/packages/isc/sandbox_interface.go +++ b/packages/isc/sandbox_interface.go @@ -241,6 +241,7 @@ type BLS interface { } type EVMTracer struct { - Tracer *tracers.Tracer - TxIndex uint64 + Tracer *tracers.Tracer + TxIndex *uint64 + BlockNumber *uint64 } diff --git a/packages/solo/evm.go b/packages/solo/evm.go index e3e17fdf55..9002810135 100644 --- a/packages/solo/evm.go +++ b/packages/solo/evm.go @@ -62,19 +62,21 @@ func (b *jsonRPCSoloBackend) EVMEstimateGas(aliasOutput *isc.AliasOutputWithID, return chainutil.EVMEstimateGas(b.Chain, aliasOutput, callMsg) } -func (b *jsonRPCSoloBackend) EVMTraceTransaction( +func (b *jsonRPCSoloBackend) EVMTrace( aliasOutput *isc.AliasOutputWithID, blockTime time.Time, iscRequestsInBlock []isc.Request, - txIndex uint64, + txIndex *uint64, + blockNumber *uint64, tracer *tracers.Tracer, ) error { - return chainutil.EVMTraceTransaction( + return chainutil.EVMTrace( b.Chain, aliasOutput, blockTime, iscRequestsInBlock, txIndex, + blockNumber, tracer, ) } diff --git a/packages/vm/core/evm/evmimpl/internal.go b/packages/vm/core/evm/evmimpl/internal.go index 2f02b9c877..7c92b7f3e0 100644 --- a/packages/vm/core/evm/evmimpl/internal.go +++ b/packages/vm/core/evm/evmimpl/internal.go @@ -36,10 +36,18 @@ func getTracer(ctx isc.Sandbox) *tracing.Hooks { if tracer == nil { return nil } - if tracer.TxIndex != uint64(ctx.RequestIndex()) { - return nil // trace only the transaction we're interested in + + // if block number is set and the tx is null, we're tracing the whole block + if tracer.TxIndex == nil && tracer.BlockNumber != nil { + return tracer.Tracer.Hooks + } + + // if tx index is set, we're tracing a specific transaction + if tracer.TxIndex != nil && *tracer.TxIndex == uint64(ctx.RequestIndex()) { + return tracer.Tracer.Hooks } - return tracer.Tracer.Hooks + + return nil } func createEmulator(ctx isc.Sandbox) *emulator.EVMEmulator { From 237db99fdef382998e11ad2c0e3cbafcdf805c65 Mon Sep 17 00:00:00 2001 From: vitaliy Date: Tue, 10 Sep 2024 15:21:32 +0300 Subject: [PATCH 2/4] Add debug_traceBlockByHash test --- packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index e481cbd049..e45af303ac 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -652,6 +652,19 @@ func TestRPCTraceBlock(t *testing.T) { ) 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_traceBlockByNumber should produce equal results") + traceBlock := make([]jsonrpc.TxTraceResult, 0) err = json.Unmarshal(res1, &traceBlock) require.NoError(t, err) From c9bb555d23bb010603db2f25718d6ad2a1d89f54 Mon Sep 17 00:00:00 2001 From: vitaliy Date: Tue, 10 Sep 2024 15:35:53 +0300 Subject: [PATCH 3/4] Fix lint --- packages/evm/jsonrpc/tracer_call.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/evm/jsonrpc/tracer_call.go b/packages/evm/jsonrpc/tracer_call.go index 0b3f7d4c73..cd05d5a29c 100644 --- a/packages/evm/jsonrpc/tracer_call.go +++ b/packages/evm/jsonrpc/tracer_call.go @@ -112,7 +112,6 @@ type callTracerConfig struct { 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 { @@ -268,12 +267,12 @@ func (t *callTracer) GetResult() (json.RawMessage, error) { }) } - resJson, err := json.Marshal(results) + resJSON, err := json.Marshal(results) if err != nil { return nil, err } - return resJson, t.reason + return resJSON, t.reason } // Stop terminates execution of the tracer at the first opportune moment. From 5bffb35519720b11e3ad68d30c9a31fb573119f4 Mon Sep 17 00:00:00 2001 From: vitaliy Date: Tue, 10 Sep 2024 17:32:13 +0300 Subject: [PATCH 4/4] Add block receipt test --- .../evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 61 +++++++++++++++++++ packages/evm/jsonrpc/service.go | 4 +- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index e45af303ac..f5eb824915 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -716,6 +716,67 @@ func TestRPCTraceBlock(t *testing.T) { require.Empty(t, innerCall2.RevertReason) } +func TestRPCBlockReceipt(t *testing.T) { + env := newSoloTestEnv(t) + creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() + creator2, creatorAddress2 := env.soloChain.NewEthereumAccountWithL2Funds() + contractABI, err := abi.JSON(strings.NewReader(evmtest.ISCTestContractABI)) + require.NoError(t, err) + _, _, contractAddress := env.DeployEVMContract(creator, contractABI, evmtest.ISCTestContractBytecode) + + 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))), + }) + + tx2 := types.MustSignNewTx(creator2, types.NewEIP155Signer(big.NewInt(int64(env.ChainID))), + &types.LegacyTx{ + Nonce: env.NonceAt(creatorAddress2), + To: &contractAddress, + Value: big.NewInt(321), + Gas: 100000, + GasPrice: big.NewInt(10000000000), + Data: lo.Must(contractABI.Pack("sendTo", common.Address{0x2}, big.NewInt(3))), + }) + + req1 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx1)) + req2 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx2)) + env.soloChain.WaitForRequestsMark() + env.soloChain.Env.AddRequestsToMempool(env.soloChain, []isc.Request{req1, req2}) + require.True(t, env.soloChain.WaitForRequestsThrough(2, 180*time.Second)) + + bi := env.soloChain.GetLatestBlockInfo() + require.EqualValues(t, 2, bi.NumSuccessfulRequests) + + var resceipts []*types.Receipt + err = env.RawClient.CallContext( + context.Background(), + &resceipts, + "eth_getBlockReceipts", + env.BlockNumber()) + require.NoError(t, err) + + require.Len(t, resceipts, 2) + + r1 := resceipts[slices.IndexFunc(resceipts, func(v *types.Receipt) bool { + return v.TxHash == tx1.Hash() + })] + + r2 := resceipts[slices.IndexFunc(resceipts, 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), r1.BlockNumber) +} + func BenchmarkRPCEstimateGas(b *testing.B) { env := newSoloTestEnv(b) _, addr := env.soloChain.NewEthereumAccountWithL2Funds() diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index bb71c9332a..7594fbb3e8 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -451,9 +451,9 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip return rpcSub, nil } -func (e *EthService) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, error) { +func (e *EthService) GetBlockReceipts(blockNumber int64) ([]*types.Receipt, error) { return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]*types.Receipt, error) { - receipts, err := e.evmChain.GetBlockReceipts(blockNumber) + receipts, err := e.evmChain.GetBlockReceipts(rpc.BlockNumber(blockNumber)) if err != nil { return nil, e.resolveError(err) }