From 58d2118c1053bb06a0c360f6b3b9ea8cb73e542e Mon Sep 17 00:00:00 2001 From: bernard-wagner <bernard-wagner@users.noreply.github.com> Date: Sat, 19 Feb 2022 10:15:27 +0200 Subject: [PATCH] rpcdaemon: added debug_traceBlockByNumber and debug_traceBlockByHash (#3548) --- cmd/rpcdaemon/README.md | 2 + cmd/rpcdaemon/commands/debug_api.go | 2 + cmd/rpcdaemon/commands/debug_api_test.go | 82 +++++++++++++++++++ cmd/rpcdaemon/commands/tracing.go | 77 ++++++++++++++++- cmd/rpctest/main.go | 10 +++ cmd/rpctest/rpctest/bench_tracetransaction.go | 57 +++++++++++++ cmd/rpctest/rpctest/request_generator.go | 5 ++ 7 files changed, 234 insertions(+), 1 deletion(-) diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index 11ecb54c53..fc9bebe1d2 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -236,6 +236,8 @@ The following table shows the current implementation status of Erigon's RPC daem | debug_getModifiedAccountsByNumber | Yes | | | debug_getModifiedAccountsByHash | Yes | | | debug_storageRangeAt | Yes | | +| debug_traceBlockByHash | Yes | Streaming (can handle huge results) | +| debug_traceBlockByNumber | Yes | Streaming (can handle huge results) | | debug_traceTransaction | Yes | Streaming (can handle huge results) | | debug_traceCall | Yes | Streaming (can handle huge results) | | | | | diff --git a/cmd/rpcdaemon/commands/debug_api.go b/cmd/rpcdaemon/commands/debug_api.go index 893967c05f..5ecad1b2ec 100644 --- a/cmd/rpcdaemon/commands/debug_api.go +++ b/cmd/rpcdaemon/commands/debug_api.go @@ -28,6 +28,8 @@ const AccountRangeMaxResults = 256 type PrivateDebugAPI interface { StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error + TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error + TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *tracers.TraceConfig, stream *jsoniter.Stream) error AccountRange(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage bool) (state.IteratorDump, error) GetModifiedAccountsByNumber(ctx context.Context, startNum rpc.BlockNumber, endNum *rpc.BlockNumber) ([]common.Address, error) GetModifiedAccountsByHash(_ context.Context, startHash common.Hash, endHash *common.Hash) ([]common.Address, error) diff --git a/cmd/rpcdaemon/commands/debug_api_test.go b/cmd/rpcdaemon/commands/debug_api_test.go index cbba7364c0..8cc7bfa0dc 100644 --- a/cmd/rpcdaemon/commands/debug_api_test.go +++ b/cmd/rpcdaemon/commands/debug_api_test.go @@ -12,6 +12,7 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/eth/tracers" "github.com/ledgerwatch/erigon/internal/ethapi" + "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/snapshotsync" ) @@ -37,6 +38,87 @@ var debugTraceTransactionNoRefundTests = []struct { {"b6449d8e167a8826d050afe4c9f07095236ff769a985f02649b1023c2ded2059", 62899, false, ""}, } +func TestTraceBlockByNumber(t *testing.T) { + db := rpcdaemontest.CreateTestKV(t) + stateCache := kvcache.New(kvcache.DefaultCoherentConfig) + baseApi := NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false) + ethApi := NewEthAPI(baseApi, db, nil, nil, nil, 5000000) + api := NewPrivateDebugAPI(baseApi, db, 0) + for _, tt := range debugTraceTransactionTests { + var buf bytes.Buffer + stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) + tx, err := ethApi.GetTransactionByHash(context.Background(), common.HexToHash(tt.txHash)) + if err != nil { + t.Errorf("traceBlock %s: %v", tt.txHash, err) + } + txcount, err := ethApi.GetBlockTransactionCountByHash(context.Background(), *tx.BlockHash) + if err != nil { + t.Errorf("traceBlock %s: %v", tt.txHash, err) + } + err = api.TraceBlockByNumber(context.Background(), rpc.BlockNumber(tx.BlockNumber.ToInt().Uint64()), &tracers.TraceConfig{}, stream) + if err != nil { + t.Errorf("traceBlock %s: %v", tt.txHash, err) + } + if err = stream.Flush(); err != nil { + t.Fatalf("error flusing: %v", err) + } + var er []ethapi.ExecutionResult + if err = json.Unmarshal(buf.Bytes(), &er); err != nil { + t.Fatalf("parsing result: %v", err) + } + if len(er) != int(*txcount) { + t.Fatalf("incorrect length: %v", err) + } + } + var buf bytes.Buffer + stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) + err := api.TraceBlockByNumber(context.Background(), rpc.BlockNumber(rpc.LatestBlockNumber), &tracers.TraceConfig{}, stream) + if err != nil { + t.Errorf("traceBlock %v: %v", rpc.LatestBlockNumber, err) + } + if err = stream.Flush(); err != nil { + t.Fatalf("error flusing: %v", err) + } + var er []ethapi.ExecutionResult + if err = json.Unmarshal(buf.Bytes(), &er); err != nil { + t.Fatalf("parsing result: %v", err) + } +} + +func TestTraceBlockByHash(t *testing.T) { + db := rpcdaemontest.CreateTestKV(t) + stateCache := kvcache.New(kvcache.DefaultCoherentConfig) + baseApi := NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false) + ethApi := NewEthAPI(baseApi, db, nil, nil, nil, 5000000) + api := NewPrivateDebugAPI(baseApi, db, 0) + for _, tt := range debugTraceTransactionTests { + var buf bytes.Buffer + stream := jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096) + tx, err := ethApi.GetTransactionByHash(context.Background(), common.HexToHash(tt.txHash)) + if err != nil { + t.Errorf("traceBlock %s: %v", tt.txHash, err) + } + txcount, err := ethApi.GetBlockTransactionCountByHash(context.Background(), *tx.BlockHash) + if err != nil { + t.Errorf("traceBlock %s: %v", tt.txHash, err) + } + err = api.TraceBlockByHash(context.Background(), *tx.BlockHash, &tracers.TraceConfig{}, stream) + if err != nil { + t.Errorf("traceBlock %s: %v", tt.txHash, err) + } + if err = stream.Flush(); err != nil { + t.Fatalf("error flusing: %v", err) + } + var er []ethapi.ExecutionResult + if err = json.Unmarshal(buf.Bytes(), &er); err != nil { + t.Fatalf("parsing result: %v", err) + } + if len(er) != int(*txcount) { + t.Fatalf("incorrect length: %v", err) + } + } +} + func TestTraceTransaction(t *testing.T) { db := rpcdaemontest.CreateTestKV(t) stateCache := kvcache.New(kvcache.DefaultCoherentConfig) diff --git a/cmd/rpcdaemon/commands/tracing.go b/cmd/rpcdaemon/commands/tracing.go index 8e44b026de..d32c7b67db 100644 --- a/cmd/rpcdaemon/commands/tracing.go +++ b/cmd/rpcdaemon/commands/tracing.go @@ -19,6 +19,82 @@ import ( "github.com/ledgerwatch/erigon/turbo/transactions" ) +// TraceBlockByNumber implements debug_traceBlockByNumber. Returns Geth style block traces. +func (api *PrivateDebugAPIImpl) TraceBlockByNumber(ctx context.Context, blockNum rpc.BlockNumber, config *tracers.TraceConfig, stream *jsoniter.Stream) error { + return api.traceBlock(ctx, rpc.BlockNumberOrHashWithNumber(blockNum), config, stream) +} + +// TraceBlockByHash implements debug_traceBlockByHash. Returns Geth style block traces. +func (api *PrivateDebugAPIImpl) TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { + return api.traceBlock(ctx, rpc.BlockNumberOrHashWithHash(hash, true), config, stream) +} + +func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { + tx, err := api.db.BeginRo(ctx) + if err != nil { + stream.WriteNil() + return err + } + defer tx.Rollback() + var block *types.Block + if number, ok := blockNrOrHash.Number(); ok { + block, err = api.blockByRPCNumber(number, tx) + } else if hash, ok := blockNrOrHash.Hash(); ok { + block, err = api.blockByHashWithSenders(tx, hash) + } else { + return fmt.Errorf("invalid arguments; neither block nor hash specified") + } + + if err != nil { + stream.WriteNil() + return err + } + + chainConfig, err := api.chainConfig(tx) + if err != nil { + stream.WriteNil() + return err + } + + contractHasTEVM := func(contractHash common.Hash) (bool, error) { return false, nil } + if api.TevmEnabled { + contractHasTEVM = ethdb.GetHasTEVM(tx) + } + + getHeader := func(hash common.Hash, number uint64) *types.Header { + return rawdb.ReadHeader(tx, hash, number) + } + + _, blockCtx, txCtx, ibs, reader, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, block.Hash(), 0) + if err != nil { + stream.WriteNil() + return err + } + + signer := types.MakeSigner(chainConfig, block.NumberU64()) + stream.WriteArrayStart() + for idx, tx := range block.Transactions() { + select { + default: + case <-ctx.Done(): + stream.WriteNil() + return ctx.Err() + } + ibs.Prepare(tx.Hash(), block.Hash(), idx) + msg, _ := tx.AsMessage(*signer, block.BaseFee()) + + transactions.TraceTx(ctx, msg, blockCtx, txCtx, ibs, config, chainConfig, stream) + _ = ibs.FinalizeTx(chainConfig.Rules(blockCtx.BlockNumber), reader) + if idx != len(block.Transactions())-1 { + stream.WriteMore() + } + stream.Flush() + } + stream.WriteArrayEnd() + stream.Flush() + return nil +} + // TraceTransaction implements debug_traceTransaction. Returns Geth style transaction traces. func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error { tx, err := api.db.BeginRo(ctx) @@ -27,7 +103,6 @@ func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash commo return err } defer tx.Rollback() - // Retrieve the transaction and assemble its EVM context blockNum, ok, err := api.txnLookup(ctx, tx, hash) if err != nil { diff --git a/cmd/rpctest/main.go b/cmd/rpctest/main.go index cb8a7d099c..53a19fe087 100644 --- a/cmd/rpctest/main.go +++ b/cmd/rpctest/main.go @@ -152,6 +152,16 @@ func main() { } with(bench9Cmd, withErigonUrl, withGethUrl, withNeedCompare) + var benchTraceBlockByHashCmd = &cobra.Command{ + Use: "benchTraceBlockByHash", + Short: "", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + rpctest.BenchTraceBlockByHash(erigonURL, gethURL, needCompare, blockFrom, blockTo, recordFile, errorFile) + }, + } + with(benchTraceBlockByHashCmd, withGethUrl, withErigonUrl, withNeedCompare, withBlockNum, withRecord, withErrorFile) + var benchTraceTransactionCmd = &cobra.Command{ Use: "benchTraceTransaction", Short: "", diff --git a/cmd/rpctest/rpctest/bench_tracetransaction.go b/cmd/rpctest/rpctest/bench_tracetransaction.go index 7158c2b07a..403e1b99b1 100644 --- a/cmd/rpctest/rpctest/bench_tracetransaction.go +++ b/cmd/rpctest/rpctest/bench_tracetransaction.go @@ -8,6 +8,63 @@ import ( "time" ) +func BenchTraceBlockByHash(erigonUrl, gethUrl string, needCompare bool, blockFrom uint64, blockTo uint64, recordFile string, errorFile string) { + setRoutes(erigonUrl, gethUrl) + var client = &http.Client{ + Timeout: time.Second * 600, + } + + var rec *bufio.Writer + if recordFile != "" { + f, err := os.Create(recordFile) + if err != nil { + fmt.Printf("Cannot create file %s for recording: %v\n", recordFile, err) + return + } + defer f.Close() + rec = bufio.NewWriter(f) + defer rec.Flush() + } + var errs *bufio.Writer + if errorFile != "" { + ferr, err := os.Create(errorFile) + if err != nil { + fmt.Printf("Cannot create file %s for error output: %v\n", errorFile, err) + return + } + defer ferr.Close() + errs = bufio.NewWriter(ferr) + defer errs.Flush() + } + + var res CallResult + reqGen := &RequestGenerator{ + client: client, + } + + reqGen.reqID++ + + for bn := blockFrom; bn < blockTo; bn++ { + var b EthBlockByNumber + res = reqGen.Erigon("eth_getBlockByNumber", reqGen.getBlockByNumber(bn), &b) + if res.Err != nil { + fmt.Printf("retrieve block (Erigon) %d: %v", blockFrom, res.Err) + return + } + if b.Error != nil { + fmt.Printf("retrieving block (Erigon): %d %s", b.Error.Code, b.Error.Message) + return + } + reqGen.reqID++ + request := reqGen.traceBlockByHash(b.Result.Hash.Hex()) + errCtx := fmt.Sprintf("block %d, tx %s", bn, b.Result.Hash.Hex()) + if err := requestAndCompare(request, "debug_traceBlockByHash", errCtx, reqGen, needCompare, rec, errs, nil); err != nil { + fmt.Println(err) + return + } + } +} + func BenchTraceTransaction(erigonUrl, gethUrl string, needCompare bool, blockFrom uint64, blockTo uint64, recordFile string, errorFile string) { setRoutes(erigonUrl, gethUrl) var client = &http.Client{ diff --git a/cmd/rpctest/rpctest/request_generator.go b/cmd/rpctest/rpctest/request_generator.go index 4dcbe3c4da..5e23c3f1a7 100644 --- a/cmd/rpctest/rpctest/request_generator.go +++ b/cmd/rpctest/rpctest/request_generator.go @@ -41,6 +41,11 @@ func (g *RequestGenerator) storageRangeAt(hash common.Hash, i int, to *common.Ad return fmt.Sprintf(template, hash, i, to, nextKey, 1024, g.reqID) } +func (g *RequestGenerator) traceBlockByHash(hash string) string { + const template = `{"jsonrpc":"2.0","method":"debug_traceBlockByHash","params":["%s"],"id":%d}` + return fmt.Sprintf(template, hash, g.reqID) +} + func (g *RequestGenerator) traceTransaction(hash string) string { const template = `{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["%s"],"id":%d}` return fmt.Sprintf(template, hash, g.reqID) -- GitLab