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