github.com/ethereum/go-ethereum@v1.16.1/cmd/workload/tracetestgen.go (about)

     1  // Copyright 2025 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>
    16  
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"math/big"
    24  	"math/rand"
    25  	"os"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/common"
    30  	"github.com/ethereum/go-ethereum/crypto"
    31  	"github.com/ethereum/go-ethereum/eth/tracers"
    32  	"github.com/ethereum/go-ethereum/eth/tracers/logger"
    33  	"github.com/ethereum/go-ethereum/internal/flags"
    34  	"github.com/ethereum/go-ethereum/log"
    35  	"github.com/urfave/cli/v2"
    36  )
    37  
    38  var (
    39  	defaultBlocksToTrace = 64 // the number of states assumed to be available
    40  
    41  	traceGenerateCommand = &cli.Command{
    42  		Name:      "tracegen",
    43  		Usage:     "Generates tests for state tracing",
    44  		ArgsUsage: "<RPC endpoint URL>",
    45  		Action:    generateTraceTests,
    46  		Flags: []cli.Flag{
    47  			traceTestFileFlag,
    48  			traceTestResultOutputFlag,
    49  			traceTestBlockFlag,
    50  		},
    51  	}
    52  
    53  	traceTestFileFlag = &cli.StringFlag{
    54  		Name:     "trace-tests",
    55  		Usage:    "JSON file containing trace test queries",
    56  		Value:    "trace_tests.json",
    57  		Category: flags.TestingCategory,
    58  	}
    59  	traceTestResultOutputFlag = &cli.StringFlag{
    60  		Name:     "trace-output",
    61  		Usage:    "Folder containing the trace output files",
    62  		Value:    "",
    63  		Category: flags.TestingCategory,
    64  	}
    65  	traceTestBlockFlag = &cli.IntFlag{
    66  		Name:     "trace-blocks",
    67  		Usage:    "The number of blocks for tracing",
    68  		Value:    defaultBlocksToTrace,
    69  		Category: flags.TestingCategory,
    70  	}
    71  	traceTestInvalidOutputFlag = &cli.StringFlag{
    72  		Name:     "trace-invalid",
    73  		Usage:    "Folder containing the mismatched trace output files",
    74  		Value:    "",
    75  		Category: flags.TestingCategory,
    76  	}
    77  )
    78  
    79  func generateTraceTests(clictx *cli.Context) error {
    80  	var (
    81  		client     = makeClient(clictx)
    82  		outputFile = clictx.String(traceTestFileFlag.Name)
    83  		outputDir  = clictx.String(traceTestResultOutputFlag.Name)
    84  		blocks     = clictx.Int(traceTestBlockFlag.Name)
    85  		ctx        = context.Background()
    86  		test       = new(traceTest)
    87  	)
    88  	if outputDir != "" {
    89  		err := os.MkdirAll(outputDir, os.ModePerm)
    90  		if err != nil {
    91  			return err
    92  		}
    93  	}
    94  	latest, err := client.Eth.BlockNumber(ctx)
    95  	if err != nil {
    96  		exit(err)
    97  	}
    98  	if latest < uint64(blocks) {
    99  		exit(fmt.Errorf("node seems not synced, latest block is %d", latest))
   100  	}
   101  	// Get blocks and assign block info into the test
   102  	var (
   103  		start  = time.Now()
   104  		logged = time.Now()
   105  		failed int
   106  	)
   107  	log.Info("Trace transactions around the chain tip", "head", latest, "blocks", blocks)
   108  
   109  	for i := 0; i < blocks; i++ {
   110  		number := latest - uint64(i)
   111  		block, err := client.Eth.BlockByNumber(ctx, big.NewInt(int64(number)))
   112  		if err != nil {
   113  			exit(err)
   114  		}
   115  		for _, tx := range block.Transactions() {
   116  			config, configName := randomTraceOption()
   117  			result, err := client.Geth.TraceTransaction(ctx, tx.Hash(), config)
   118  			if err != nil {
   119  				failed += 1
   120  				break
   121  			}
   122  			blob, err := json.Marshal(result)
   123  			if err != nil {
   124  				failed += 1
   125  				break
   126  			}
   127  			test.TxHashes = append(test.TxHashes, tx.Hash())
   128  			test.TraceConfigs = append(test.TraceConfigs, *config)
   129  			test.ResultHashes = append(test.ResultHashes, crypto.Keccak256Hash(blob))
   130  			writeTraceResult(outputDir, tx.Hash(), result, configName)
   131  		}
   132  		if time.Since(logged) > time.Second*8 {
   133  			logged = time.Now()
   134  			log.Info("Tracing transactions", "executed", len(test.TxHashes), "failed", failed, "elapsed", common.PrettyDuration(time.Since(start)))
   135  		}
   136  	}
   137  	log.Info("Traced transactions", "executed", len(test.TxHashes), "failed", failed, "elapsed", common.PrettyDuration(time.Since(start)))
   138  
   139  	// Write output file.
   140  	writeJSON(outputFile, test)
   141  	return nil
   142  }
   143  
   144  func randomTraceOption() (*tracers.TraceConfig, string) {
   145  	x := rand.Intn(11)
   146  	if x == 0 {
   147  		// struct-logger, with all fields enabled, very heavy
   148  		return &tracers.TraceConfig{
   149  			Config: &logger.Config{
   150  				EnableMemory:     true,
   151  				EnableReturnData: true,
   152  			},
   153  		}, "structAll"
   154  	}
   155  	if x == 1 {
   156  		// default options for struct-logger, with stack and storage capture
   157  		// enabled
   158  		return &tracers.TraceConfig{
   159  			Config: &logger.Config{},
   160  		}, "structDefault"
   161  	}
   162  	if x == 2 || x == 3 || x == 4 {
   163  		// struct-logger with storage capture enabled
   164  		return &tracers.TraceConfig{
   165  			Config: &logger.Config{
   166  				DisableStack: true,
   167  			},
   168  		}, "structStorage"
   169  	}
   170  	// Native tracer
   171  	loggers := []string{"callTracer", "4byteTracer", "flatCallTracer", "muxTracer", "noopTracer", "prestateTracer"}
   172  	return &tracers.TraceConfig{
   173  		Tracer: &loggers[x-5],
   174  	}, loggers[x-5]
   175  }
   176  
   177  func writeTraceResult(dir string, hash common.Hash, result any, configName string) {
   178  	if dir == "" {
   179  		return
   180  	}
   181  	name := filepath.Join(dir, configName+"_"+hash.String())
   182  	file, err := os.Create(name)
   183  	if err != nil {
   184  		exit(fmt.Errorf("error creating %s: %v", name, err))
   185  		return
   186  	}
   187  	defer file.Close()
   188  
   189  	data, _ := json.MarshalIndent(result, "", "    ")
   190  	_, err = file.Write(data)
   191  	if err != nil {
   192  		exit(fmt.Errorf("error writing %s: %v", name, err))
   193  		return
   194  	}
   195  }