From 1bd990d32e0987b6fcc5adc6f56b786753094ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Fri, 18 Oct 2024 12:47:30 +0200 Subject: [PATCH 01/14] Update getBlockReceipts --- packages/evm/jsonrpc/evmchain.go | 7 ++++--- packages/evm/jsonrpc/service.go | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index 7f235058af..de8a5fd312 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -745,16 +745,17 @@ func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceC return e.Trace(config, nil, common.Hash{}, blockNumber, block.Hash()) } -func (e *EVMChain) GetBlockReceipts(blockNumber rpc.BlockNumber) ([]*types.Receipt, error) { +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) if err != nil { - return nil, err + return nil, nil, err } db := blockchainDB(chainState) - return db.GetReceiptsByBlockNumber(bn.Uint64()), nil + + return db.GetReceiptsByBlockNumber(bn.Uint64()), db.GetTransactionsByBlockNumber(bn.Uint64()), nil } var maxUint32 = big.NewInt(math.MaxUint32) diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index 1174e2d04c..1c12d8d05d 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -453,11 +453,17 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip func (e *EthService) GetBlockReceipts(blockNumber int64) ([]*types.Receipt, error) { return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]*types.Receipt, error) { - receipts, err := e.evmChain.GetBlockReceipts(rpc.BlockNumber(blockNumber)) + receipts, txs, err := e.evmChain.GetBlockReceipts(rpc.BlockNumber(blockNumber)) if err != nil { return nil, e.resolveError(err) } + result := make([]map[string]interface{}, len(receipts)) + for i, receipt := range receipts { + effectiveGasPrice := txs[i].GasPrice() + result[i] = RPCMarshalReceipt(receipt, txs[i], effectiveGasPrice) + } + return receipts, nil }) } From 5ed3b998fbf9c4f824e3ec452fdf31225a07bc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Fri, 18 Oct 2024 19:48:26 +0200 Subject: [PATCH 02/14] Fix eth_getBlockReceipts * Include transactions * Properly format compatible output --- packages/evm/jsonrpc/service.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index 1c12d8d05d..bcf08e138c 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -451,11 +451,15 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip return rpcSub, nil } -func (e *EthService) GetBlockReceipts(blockNumber int64) ([]*types.Receipt, error) { - return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]*types.Receipt, error) { +func (e *EthService) GetBlockReceipts(blockNumber hexutil.Uint64) ([]map[string]interface{}, error) { + return withMetrics(e.metrics, "eth_getBlockReceipts", func() ([]map[string]interface{}, error) { receipts, txs, err := e.evmChain.GetBlockReceipts(rpc.BlockNumber(blockNumber)) if err != nil { - return nil, e.resolveError(err) + return []map[string]interface{}{}, e.resolveError(err) + } + + if len(receipts) != len(txs) { + return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(receipts), len(txs)) } result := make([]map[string]interface{}, len(receipts)) @@ -464,7 +468,7 @@ func (e *EthService) GetBlockReceipts(blockNumber int64) ([]*types.Receipt, erro result[i] = RPCMarshalReceipt(receipt, txs[i], effectiveGasPrice) } - return receipts, nil + return result, nil }) } From 910b1d3bcfa92c70f866c7568d0b26b5acdf789a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Fri, 18 Oct 2024 19:52:42 +0200 Subject: [PATCH 03/14] Fix `CallFrame` output for debug_Trace funcs * Format fields as Hex instead of Base64 --- packages/evm/jsonrpc/tracer_call.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/evm/jsonrpc/tracer_call.go b/packages/evm/jsonrpc/tracer_call.go index 3330435434..570f8366b6 100644 --- a/packages/evm/jsonrpc/tracer_call.go +++ b/packages/evm/jsonrpc/tracer_call.go @@ -60,8 +60,8 @@ type CallFrame struct { Gas hexutil.Uint64 `json:"gas"` GasUsed hexutil.Uint64 `json:"gasUsed"` To *common.Address `json:"to,omitempty" rlp:"optional"` - Input []byte `json:"input" rlp:"optional"` - Output []byte `json:"output,omitempty" rlp:"optional"` + Input hexutil.Bytes `json:"input" rlp:"optional"` + Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"` Error string `json:"error,omitempty" rlp:"optional"` RevertReason string `json:"revertReason,omitempty"` Calls []CallFrame `json:"calls,omitempty" rlp:"optional"` From 1497e832e6da9ab0fdc8942c0b4f8175cd07b193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Sat, 19 Oct 2024 16:54:57 +0200 Subject: [PATCH 04/14] Fix build-test workflow to fix wasm related errors --- .github/workflows/build-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 0c63be3f5c..6d050e5738 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: "1.23.2" id: go - name: Check out code into the Go module directory @@ -44,16 +44,16 @@ jobs: - name: install golang uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: "1.23.2" - name: install rust-toolchain uses: actions-rs/toolchain@v1.0.7 with: - toolchain: stable + toolchain: 1.80.0 - name: install wasm-pack run: | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + curl https://raw.githubusercontent.com/rustwasm/wasm-pack/refs/heads/master/docs/_installer/init.sh -sSf | env VERSION=v0.13.0 sh - name: install schema run: | From 6c721695dfeabc1b352f2c2a5eaab6173ed5ca38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Sat, 19 Oct 2024 17:05:54 +0200 Subject: [PATCH 05/14] Update JSON-RPC response --- packages/evm/jsonrpc/service.go | 10 ++++++++++ packages/evm/jsonrpc/types.go | 1 + 2 files changed, 11 insertions(+) diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index bcf08e138c..44624394c2 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -453,6 +453,11 @@ func (e *EthService) Logs(ctx context.Context, q *RPCFilterQuery) (*rpc.Subscrip func (e *EthService) GetBlockReceipts(blockNumber hexutil.Uint64) ([]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)) if err != nil { return []map[string]interface{}{}, e.resolveError(err) @@ -465,6 +470,11 @@ func (e *EthService) GetBlockReceipts(blockNumber hexutil.Uint64) ([]map[string] result := make([]map[string]interface{}, len(receipts)) for i, receipt := range receipts { effectiveGasPrice := txs[i].GasPrice() + if effectiveGasPrice.Sign() == 0 && !feePolicy.GasPerToken.IsEmpty() { + // tx sent before gasPrice was mandatory + effectiveGasPrice = feePolicy.DefaultGasPriceFullDecimals(parameters.L1().BaseToken.Decimals) + } + result[i] = RPCMarshalReceipt(receipt, txs[i], effectiveGasPrice) } diff --git a/packages/evm/jsonrpc/types.go b/packages/evm/jsonrpc/types.go index 5434daf2bf..0f0ae1201a 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -195,6 +195,7 @@ func rpcMarshalLogs(r *types.Receipt) []interface{} { "address": log.Address, "data": hexutil.Bytes(log.Data), "topics": log.Topics, + "removed": log.Removed, } } return ret From 3ea77451119817647cebcd81f7725b9176e0e5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Sat, 19 Oct 2024 17:06:03 +0200 Subject: [PATCH 06/14] Fix jsonrpc tests --- packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index 335f7d8120..61143a374a 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -589,7 +589,7 @@ func TestRPCTraceTx(t *testing.T) { require.Equal(t, "0x7b", trace1.Value.String()) expectedInput, err := contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(1)) require.NoError(t, err) - require.Equal(t, expectedInput, trace1.Input) + require.Equal(t, expectedInput, []byte(trace1.Input)) require.Empty(t, trace1.Error) require.Empty(t, trace1.RevertReason) require.Equal(t, "0x0", trace1.Gas.String()) @@ -725,7 +725,7 @@ func TestRPCTraceBlock(t *testing.T) { 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, trace1.Input) + require.Equal(t, expectedInput, []byte(trace1.Input)) require.Empty(t, trace1.Error) require.Empty(t, trace1.RevertReason) require.Equal(t, "0x0", trace1.Gas.String()) @@ -747,7 +747,7 @@ func TestRPCTraceBlock(t *testing.T) { require.Equal(t, "0x141", trace2.Value.String()) expectedInput, err = contractABI.Pack("sendTo", common.Address{0x2}, big.NewInt(3)) require.NoError(t, err) - require.Equal(t, expectedInput, trace2.Input) + require.Equal(t, expectedInput, []byte(trace2.Input)) require.Empty(t, trace2.Error) require.Empty(t, trace2.RevertReason) require.Equal(t, "0x0", trace2.Gas.String()) @@ -807,7 +807,7 @@ func TestRPCBlockReceipt(t *testing.T) { context.Background(), &resceipts, "eth_getBlockReceipts", - env.BlockNumber()) + hexutil.EncodeUint64(env.BlockNumber())) require.NoError(t, err) require.Len(t, resceipts, 2) From 291c84dffb4daf48d3af46fd8d2c5dade3f10cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 01:05:45 +0200 Subject: [PATCH 07/14] Fix tracer and Trace RPC calls --- packages/evm/jsonrpc/evmchain.go | 136 ++++++++++---- .../evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 168 +++++++++++++----- packages/evm/jsonrpc/service.go | 27 ++- packages/evm/jsonrpc/tracer.go | 6 +- packages/evm/jsonrpc/tracer_call.go | 54 ++---- 5 files changed, 263 insertions(+), 128 deletions(-) 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. From 29b551cff62568e8f1c1105c499abb5a2579d2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 01:11:55 +0200 Subject: [PATCH 08/14] Fix linter --- packages/evm/jsonrpc/evmchain.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index b5c1c18604..d1b5a3080e 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -787,7 +787,6 @@ func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Byt 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 } @@ -808,7 +807,6 @@ func (e *EVMChain) GetBlockReceipts(blockNrOrHash rpc.BlockNumberOrHash) ([]*typ block = e.BlockByHash(h) } else if n, ok := blockNrOrHash.Number(); ok { block, err = e.BlockByNumber(parseBlockNumber(n)) - if err != nil { return nil, nil, err } From af5ccee34c4500bfa6d4110a2843d22b06c7ba11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 18:50:39 +0200 Subject: [PATCH 09/14] Finish fake-evm transaction calls --- packages/evm/jsonrpc/evmchain.go | 58 +++++++++++++------ .../evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 44 ++++++++++++++ packages/evm/jsonrpc/types.go | 13 +++++ 3 files changed, 97 insertions(+), 18 deletions(-) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index d1b5a3080e..ac744ac095 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -10,6 +10,7 @@ import ( "math" "math/big" "path" + "slices" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -665,12 +666,26 @@ func (e *EVMChain) iscRequestsInBlock(evmBlockNumber uint64) (*blocklog.BlockInf return blocklog.GetRequestsInBlock(blocklogStatePartition, iscBlockIndex) } -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) { +func (e *EVMChain) isFakeTransaction(tx *types.Transaction) bool { + sender, err := evmutil.GetSender(tx) + + // the error will fire when the transaction is invalid. This is most of the time a fake evm tx we use for internal calls, therefore it's fine to assume both. + if slices.Equal(sender.Bytes(), common.Address{}.Bytes()) || err != nil { + return true + } + + return false +} + +// Trace allows the tracing of EVM transactions and considers "fake" evm transactions that are emitted when ISC internal requests are being made. (Transfer of funds from L1->L2EVM for example) +func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockInfo, requestsInBlock []isc.Request, evmTxs types.Transactions, txIndex uint64, txHash common.Hash, blockHash common.Hash) (json.RawMessage, error) { tracerType := "callTracer" if config.Tracer != nil { tracerType = *config.Tracer } + blockNumber := uint64(blockInfo.BlockIndex()) + tracer, err := newTracer(tracerType, &tracers.Context{ BlockHash: blockHash, BlockNumber: new(big.Int).SetUint64(blockNumber), @@ -689,11 +704,24 @@ func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockI &blockNumber, tracer, ) + + result, err := tracer.GetResult() if err != nil { - return nil, err + if err != nil && !errors.Is(err, ErrIncorrectTopLevelCalls) { + return nil, err + } + + tx, ok := lo.Find(evmTxs, func(tx *types.Transaction) bool { return slices.Equal(txHash.Bytes(), tx.Hash().Bytes()) }) + if !ok { + return nil, fmt.Errorf("can not find transaction: %v", txHash.String()) + } + + if e.isFakeTransaction(tx) { + return json.Marshal(RPCMarshalTransactionForFakeTX(tx, tx.GasPrice())) + } } - return tracer.GetResult() + return result, nil } func (e *EVMChain) traceTransaction(config *tracers.TraceConfig, txIndex uint64, txHash common.Hash, blockNumber uint64, blockHash common.Hash) (any, error) { @@ -702,42 +730,36 @@ func (e *EVMChain) traceTransaction(config *tracers.TraceConfig, txIndex uint64, return nil, err } - result, err := e.trace(config, iscBlock, iscRequestsInBlock, txIndex, txHash, blockNumber, blockHash) + blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber)) if err != nil { return nil, err } - return result, nil + return e.trace(config, iscBlock, iscRequestsInBlock, blockTxs, txIndex, txHash, blockHash) } -func (e *EVMChain) traceBlock(config *tracers.TraceConfig, blockNumber uint64, blockHash common.Hash) (any, error) { - iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(blockNumber) +func (e *EVMChain) traceBlock(config *tracers.TraceConfig, block *types.Block) (any, error) { + iscBlock, iscRequestsInBlock, err := e.iscRequestsInBlock(block.NumberU64()) if err != nil { return nil, err } - blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(blockNumber)) + blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(block.NumberU64())) 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) + result, err := e.trace(config, iscBlock, iscRequestsInBlock, blockTxs, uint64(i), tx.Hash(), block.Hash()) + // Transactions which failed tracing will be omitted, so the rest of the block can be returned 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 @@ -765,7 +787,7 @@ func (e *EVMChain) TraceBlockByHash(blockHash common.Hash, config *tracers.Trace return nil, fmt.Errorf("block not found: %s", blockHash.String()) } - return e.traceBlock(config, block.Number().Uint64(), blockHash) + return e.traceBlock(config, block) } func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceConfig) (any, error) { @@ -776,7 +798,7 @@ func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceC return nil, fmt.Errorf("block not found: %d", blockNumber) } - return e.traceBlock(config, blockNumber, block.Hash()) + return e.traceBlock(config, block) } func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index 1daac43941..3ba3632d4c 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -6,6 +6,7 @@ package jsonrpctest import ( "context" "encoding/json" + "fmt" "math/big" "slices" "strings" @@ -607,6 +608,49 @@ func TestRPCTraceTx(t *testing.T) { require.Contains(t, trace2.GasUsed.String(), "0x") } +// Transfer calls produce "fake" Transactions to simulate EVM behavior. +// They are not real in the sense of being persisted to the blockchain, therefore requires additional checks. +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.SendTxArgs + err = json.Unmarshal(res1, &trace1) + require.NoError(t, err) + + fmt.Print(hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens)) + + require.Equal(t, evmAddr.String(), trace1.To.String()) + require.Equal(t, hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens*1e12), trace1.Value.String()) +} + func TestRPCTraceBlock(t *testing.T) { env := newSoloTestEnv(t) creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds() diff --git a/packages/evm/jsonrpc/types.go b/packages/evm/jsonrpc/types.go index 0f0ae1201a..5cd3a6ed52 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -15,6 +15,7 @@ import ( "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/core/vm" "github.com/ethereum/go-ethereum/rpc" iotago "github.com/iotaledger/iota.go/v3" @@ -158,6 +159,18 @@ func parseBlockNumber(bn rpc.BlockNumber) *big.Int { return big.NewInt(n) } +func RPCMarshalTransactionForFakeTX(tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { + return map[string]interface{}{ + "from": evmutil.MustGetSenderIfTxSigned(tx), + "gas": hexutil.Uint64(tx.Gas()), + "gasUsed": hexutil.Uint64(tx.Gas()), + "to": tx.To(), + "input": hexutil.Bytes(tx.Data()), + "type": vm.OpCode(tx.Type()).String(), + "value": hexutil.Big(*tx.Value()), + } +} + func RPCMarshalReceipt(r *types.Receipt, tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { // fix for an already fixed bug where some old failed receipts contain non-empty logs if r.Status != types.ReceiptStatusSuccessful { From f55b2a99e2ba2480f4154c4a9210718eb5ad112a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 18:59:54 +0200 Subject: [PATCH 10/14] Validate error --- packages/evm/jsonrpc/evmchain.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index ac744ac095..20509b73a6 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -704,6 +704,9 @@ func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockI &blockNumber, tracer, ) + if err != nil { + return nil, err + } result, err := tracer.GetResult() if err != nil { From 6efd5efabae37cc61ab736a3f26de0abf26fd3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 19:21:32 +0200 Subject: [PATCH 11/14] Refactor naming --- packages/evm/jsonrpc/evmchain.go | 4 ++-- packages/evm/jsonrpc/types.go | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index 20509b73a6..9d651d4632 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -710,7 +710,7 @@ func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockI result, err := tracer.GetResult() if err != nil { - if err != nil && !errors.Is(err, ErrIncorrectTopLevelCalls) { + if !errors.Is(err, ErrIncorrectTopLevelCalls) { return nil, err } @@ -720,7 +720,7 @@ func (e *EVMChain) trace(config *tracers.TraceConfig, blockInfo *blocklog.BlockI } if e.isFakeTransaction(tx) { - return json.Marshal(RPCMarshalTransactionForFakeTX(tx, tx.GasPrice())) + return json.Marshal(RPCMarshalTransactionTraceForFakeTX(tx, tx.GasPrice())) } } diff --git a/packages/evm/jsonrpc/types.go b/packages/evm/jsonrpc/types.go index 5cd3a6ed52..10418e05f8 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -15,7 +15,6 @@ import ( "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/core/vm" "github.com/ethereum/go-ethereum/rpc" iotago "github.com/iotaledger/iota.go/v3" @@ -159,14 +158,16 @@ func parseBlockNumber(bn rpc.BlockNumber) *big.Int { return big.NewInt(n) } -func RPCMarshalTransactionForFakeTX(tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { +const FakeTxOpcode = "STOP" + +func RPCMarshalTransactionTraceForFakeTX(tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { return map[string]interface{}{ "from": evmutil.MustGetSenderIfTxSigned(tx), "gas": hexutil.Uint64(tx.Gas()), "gasUsed": hexutil.Uint64(tx.Gas()), "to": tx.To(), "input": hexutil.Bytes(tx.Data()), - "type": vm.OpCode(tx.Type()).String(), + "type": FakeTxOpcode, "value": hexutil.Big(*tx.Value()), } } From 00e52dcb1701cdab674a103718f59ed12f7f35bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 20:19:43 +0200 Subject: [PATCH 12/14] Remove Input field for fake STOP TX --- packages/evm/jsonrpc/types.go | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/evm/jsonrpc/types.go b/packages/evm/jsonrpc/types.go index 10418e05f8..4537862bac 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -166,7 +166,6 @@ func RPCMarshalTransactionTraceForFakeTX(tx *types.Transaction, effectiveGasPric "gas": hexutil.Uint64(tx.Gas()), "gasUsed": hexutil.Uint64(tx.Gas()), "to": tx.To(), - "input": hexutil.Bytes(tx.Data()), "type": FakeTxOpcode, "value": hexutil.Big(*tx.Value()), } From e5030144e49a5c060ab7986ecf04f99480b5013d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Mon, 21 Oct 2024 20:21:39 +0200 Subject: [PATCH 13/14] Remove unimplemented TraceBlock call --- packages/evm/jsonrpc/service.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/evm/jsonrpc/service.go b/packages/evm/jsonrpc/service.go index 112e3e5873..ade2fc8810 100644 --- a/packages/evm/jsonrpc/service.go +++ b/packages/evm/jsonrpc/service.go @@ -593,12 +593,6 @@ func (d *DebugService) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (interfa }) } -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 } From 3bc8146cb2ade009cd431c7d7ea5153819430650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Tue, 22 Oct 2024 15:36:08 +0200 Subject: [PATCH 14/14] Add `latest` to blockHashOrNumber calls, force null values for empty contractAddresses, change FakeTX OpCode to CALL --- packages/evm/jsonrpc/evmchain.go | 47 ++++++++++--------- .../evm/jsonrpc/jsonrpctest/jsonrpc_test.go | 23 +++++++++ packages/evm/jsonrpc/types.go | 16 +++++-- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/packages/evm/jsonrpc/evmchain.go b/packages/evm/jsonrpc/evmchain.go index 9d651d4632..55790c49ce 100644 --- a/packages/evm/jsonrpc/evmchain.go +++ b/packages/evm/jsonrpc/evmchain.go @@ -804,19 +804,29 @@ func (e *EVMChain) TraceBlockByNumber(blockNumber uint64, config *tracers.TraceC return e.traceBlock(config, block) } -func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { - var block *types.Block - var err error - +func (e *EVMChain) getBlockByNumberOrHash(blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { if h, ok := blockNrOrHash.Hash(); ok { - block = e.BlockByHash(h) + return e.BlockByHash(h), nil } else if n, ok := blockNrOrHash.Number(); ok { - block, err = e.BlockByNumber(big.NewInt(n.Int64())) - if err != nil { - return nil, err + switch n { + case rpc.LatestBlockNumber: + return e.BlockByNumber(nil) + default: + if n < 0 { + return nil, fmt.Errorf("%v is unsupported", blockNrOrHash.String()) + } + + return e.BlockByNumber(big.NewInt(n.Int64())) } - } else { - return nil, fmt.Errorf("block not found: %v", blockNrOrHash.String()) + } + + return nil, fmt.Errorf("block not found: %v", blockNrOrHash.String()) +} + +func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + block, err := e.getBlockByNumberOrHash(blockNrOrHash) + if err != nil { + return nil, err } return rlp.EncodeToBytes(block) @@ -825,21 +835,12 @@ func (e *EVMChain) GetRawBlock(blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Byt 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()) + block, err := e.getBlockByNumberOrHash(blockNrOrHash) + if err != nil { + return nil, nil, err } - chainState, err := e.iscStateFromEVMBlockNumberOrHash(&blockNrOrHash) + chainState, err := e.iscStateFromEVMBlockNumber(block.Number()) if err != nil { return nil, nil, err } diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index 3ba3632d4c..afa7190ec7 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -944,6 +944,29 @@ func TestRPCBlockReceipt(t *testing.T) { require.Equal(t, big.NewInt(4), r1.BlockNumber) require.Equal(t, uint64(1), r2.Status) require.Equal(t, big.NewInt(4), r2.BlockNumber) + + // Test "latest" block + err = env.RawClient.CallContext( + context.Background(), + &receipts, + "eth_getBlockReceipts", + "latest") + 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/types.go b/packages/evm/jsonrpc/types.go index 4537862bac..c8cba20f48 100644 --- a/packages/evm/jsonrpc/types.go +++ b/packages/evm/jsonrpc/types.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "math/big" + "slices" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -158,7 +159,7 @@ func parseBlockNumber(bn rpc.BlockNumber) *big.Int { return big.NewInt(n) } -const FakeTxOpcode = "STOP" +const FakeTxOpcode = "CALL" func RPCMarshalTransactionTraceForFakeTX(tx *types.Transaction, effectiveGasPrice *big.Int) map[string]interface{} { return map[string]interface{}{ @@ -167,6 +168,7 @@ func RPCMarshalTransactionTraceForFakeTX(tx *types.Transaction, effectiveGasPric "gasUsed": hexutil.Uint64(tx.Gas()), "to": tx.To(), "type": FakeTxOpcode, + "input": "0x", "value": hexutil.Big(*tx.Value()), } } @@ -178,7 +180,7 @@ func RPCMarshalReceipt(r *types.Receipt, tx *types.Transaction, effectiveGasPric r.Bloom = types.CreateBloom(types.Receipts{r}) } - return map[string]interface{}{ + result := map[string]interface{}{ "transactionHash": r.TxHash, "transactionIndex": hexutil.Uint64(r.TransactionIndex), "blockHash": r.BlockHash, @@ -188,12 +190,20 @@ func RPCMarshalReceipt(r *types.Receipt, tx *types.Transaction, effectiveGasPric "cumulativeGasUsed": hexutil.Uint64(r.CumulativeGasUsed), "gasUsed": hexutil.Uint64(r.GasUsed), "effectiveGasPrice": hexutil.EncodeBig(effectiveGasPrice), - "contractAddress": r.ContractAddress, "logs": rpcMarshalLogs(r), "logsBloom": r.Bloom, "status": hexutil.Uint64(r.Status), "type": hexutil.Uint64(types.LegacyTxType), } + + // Eth compatibility. Return "null" instead of "0x00000000000000000000000..." + if slices.Equal(r.ContractAddress.Bytes(), common.Address{}.Bytes()) { + result["contractAddress"] = nil + } else { + result["contractAddress"] = r.ContractAddress + } + + return result } func rpcMarshalLogs(r *types.Receipt) []interface{} {