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 }