github.com/ethereum/go-ethereum@v1.16.1/cmd/evm/runner.go (about) 1 // Copyright 2017 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 "bytes" 21 "encoding/hex" 22 "encoding/json" 23 "fmt" 24 "io" 25 "math/big" 26 "os" 27 goruntime "runtime" 28 "slices" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/ethereum/go-ethereum/cmd/utils" 34 "github.com/ethereum/go-ethereum/common" 35 "github.com/ethereum/go-ethereum/core" 36 "github.com/ethereum/go-ethereum/core/rawdb" 37 "github.com/ethereum/go-ethereum/core/state" 38 "github.com/ethereum/go-ethereum/core/tracing" 39 "github.com/ethereum/go-ethereum/core/types" 40 "github.com/ethereum/go-ethereum/core/vm" 41 "github.com/ethereum/go-ethereum/core/vm/runtime" 42 "github.com/ethereum/go-ethereum/internal/flags" 43 "github.com/ethereum/go-ethereum/params" 44 "github.com/ethereum/go-ethereum/triedb" 45 "github.com/ethereum/go-ethereum/triedb/hashdb" 46 "github.com/urfave/cli/v2" 47 ) 48 49 var runCommand = &cli.Command{ 50 Action: runCmd, 51 Name: "run", 52 Usage: "Run arbitrary evm binary", 53 ArgsUsage: "<code>", 54 Description: `The run command runs arbitrary EVM code.`, 55 Flags: slices.Concat([]cli.Flag{ 56 BenchFlag, 57 CodeFileFlag, 58 CreateFlag, 59 GasFlag, 60 GenesisFlag, 61 InputFlag, 62 InputFileFlag, 63 PriceFlag, 64 ReceiverFlag, 65 SenderFlag, 66 ValueFlag, 67 StatDumpFlag, 68 DumpFlag, 69 }, traceFlags), 70 } 71 72 var ( 73 CodeFileFlag = &cli.StringFlag{ 74 Name: "codefile", 75 Usage: "File containing EVM code. If '-' is specified, code is read from stdin ", 76 Category: flags.VMCategory, 77 } 78 CreateFlag = &cli.BoolFlag{ 79 Name: "create", 80 Usage: "Indicates the action should be create rather than call", 81 Category: flags.VMCategory, 82 } 83 GasFlag = &cli.Uint64Flag{ 84 Name: "gas", 85 Usage: "Gas limit for the evm", 86 Value: 10000000000, 87 Category: flags.VMCategory, 88 } 89 GenesisFlag = &cli.StringFlag{ 90 Name: "prestate", 91 Usage: "JSON file with prestate (genesis) config", 92 Category: flags.VMCategory, 93 } 94 InputFlag = &cli.StringFlag{ 95 Name: "input", 96 Usage: "Input for the EVM", 97 Category: flags.VMCategory, 98 } 99 InputFileFlag = &cli.StringFlag{ 100 Name: "inputfile", 101 Usage: "File containing input for the EVM", 102 Category: flags.VMCategory, 103 } 104 PriceFlag = &flags.BigFlag{ 105 Name: "price", 106 Usage: "Price set for the evm", 107 Value: new(big.Int), 108 Category: flags.VMCategory, 109 } 110 ReceiverFlag = &cli.StringFlag{ 111 Name: "receiver", 112 Usage: "The transaction receiver (execution context)", 113 Category: flags.VMCategory, 114 } 115 SenderFlag = &cli.StringFlag{ 116 Name: "sender", 117 Usage: "The transaction origin", 118 Category: flags.VMCategory, 119 } 120 ValueFlag = &flags.BigFlag{ 121 Name: "value", 122 Usage: "Value set for the evm", 123 Value: new(big.Int), 124 Category: flags.VMCategory, 125 } 126 ) 127 128 // readGenesis will read the given JSON format genesis file and return 129 // the initialized Genesis structure 130 func readGenesis(genesisPath string) *core.Genesis { 131 // Make sure we have a valid genesis JSON 132 if len(genesisPath) == 0 { 133 utils.Fatalf("Must supply path to genesis JSON file") 134 } 135 file, err := os.Open(genesisPath) 136 if err != nil { 137 utils.Fatalf("Failed to read genesis file: %v", err) 138 } 139 defer file.Close() 140 141 genesis := new(core.Genesis) 142 if err := json.NewDecoder(file).Decode(genesis); err != nil { 143 utils.Fatalf("invalid genesis file: %v", err) 144 } 145 return genesis 146 } 147 148 type execStats struct { 149 Time time.Duration `json:"time"` // The execution Time. 150 Allocs int64 `json:"allocs"` // The number of heap allocations during execution. 151 BytesAllocated int64 `json:"bytesAllocated"` // The cumulative number of bytes allocated during execution. 152 GasUsed uint64 `json:"gasUsed"` // the amount of gas used during execution 153 } 154 155 func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, execStats, error) { 156 if bench { 157 testing.Init() 158 // Do one warm-up run 159 output, gasUsed, err := execFunc() 160 result := testing.Benchmark(func(b *testing.B) { 161 for i := 0; i < b.N; i++ { 162 haveOutput, haveGasUsed, haveErr := execFunc() 163 if !bytes.Equal(haveOutput, output) { 164 panic(fmt.Sprintf("output differs\nhave %x\nwant %x\n", haveOutput, output)) 165 } 166 if haveGasUsed != gasUsed { 167 panic(fmt.Sprintf("gas differs, have %v want %v", haveGasUsed, gasUsed)) 168 } 169 if haveErr != err { 170 panic(fmt.Sprintf("err differs, have %v want %v", haveErr, err)) 171 } 172 } 173 }) 174 // Get the average execution time from the benchmarking result. 175 // There are other useful stats here that could be reported. 176 stats := execStats{ 177 Time: time.Duration(result.NsPerOp()), 178 Allocs: result.AllocsPerOp(), 179 BytesAllocated: result.AllocedBytesPerOp(), 180 GasUsed: gasUsed, 181 } 182 return output, stats, err 183 } 184 var memStatsBefore, memStatsAfter goruntime.MemStats 185 goruntime.ReadMemStats(&memStatsBefore) 186 t0 := time.Now() 187 output, gasUsed, err := execFunc() 188 duration := time.Since(t0) 189 goruntime.ReadMemStats(&memStatsAfter) 190 stats := execStats{ 191 Time: duration, 192 Allocs: int64(memStatsAfter.Mallocs - memStatsBefore.Mallocs), 193 BytesAllocated: int64(memStatsAfter.TotalAlloc - memStatsBefore.TotalAlloc), 194 GasUsed: gasUsed, 195 } 196 return output, stats, err 197 } 198 199 func runCmd(ctx *cli.Context) error { 200 var ( 201 tracer *tracing.Hooks 202 prestate *state.StateDB 203 chainConfig *params.ChainConfig 204 sender = common.BytesToAddress([]byte("sender")) 205 receiver = common.BytesToAddress([]byte("receiver")) 206 preimages = ctx.Bool(DumpFlag.Name) 207 blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests 208 blobBaseFee = new(big.Int) // TODO (MariusVanDerWijden) implement blob fee in state tests 209 ) 210 tracer = tracerFromFlags(ctx) 211 initialGas := ctx.Uint64(GasFlag.Name) 212 genesisConfig := new(core.Genesis) 213 genesisConfig.GasLimit = initialGas 214 if ctx.String(GenesisFlag.Name) != "" { 215 genesisConfig = readGenesis(ctx.String(GenesisFlag.Name)) 216 if genesisConfig.GasLimit != 0 { 217 initialGas = genesisConfig.GasLimit 218 } 219 } else { 220 genesisConfig.Config = params.AllDevChainProtocolChanges 221 } 222 223 db := rawdb.NewMemoryDatabase() 224 triedb := triedb.NewDatabase(db, &triedb.Config{ 225 Preimages: preimages, 226 HashDB: hashdb.Defaults, 227 }) 228 defer triedb.Close() 229 genesis := genesisConfig.MustCommit(db, triedb) 230 sdb := state.NewDatabase(triedb, nil) 231 prestate, _ = state.New(genesis.Root(), sdb) 232 chainConfig = genesisConfig.Config 233 234 if ctx.String(SenderFlag.Name) != "" { 235 sender = common.HexToAddress(ctx.String(SenderFlag.Name)) 236 } 237 238 if ctx.String(ReceiverFlag.Name) != "" { 239 receiver = common.HexToAddress(ctx.String(ReceiverFlag.Name)) 240 } 241 242 var code []byte 243 codeFileFlag := ctx.String(CodeFileFlag.Name) 244 hexcode := ctx.Args().First() 245 246 // The '--codefile' flag overrides code in state 247 if codeFileFlag == "-" { 248 // If - is specified, it means that code comes from stdin 249 // Try reading from stdin 250 input, err := io.ReadAll(os.Stdin) 251 if err != nil { 252 fmt.Printf("Could not load code from stdin: %v\n", err) 253 os.Exit(1) 254 } 255 hexcode = string(input) 256 } else if codeFileFlag != "" { 257 // Codefile with hex assembly 258 input, err := os.ReadFile(codeFileFlag) 259 if err != nil { 260 fmt.Printf("Could not load code from file: %v\n", err) 261 os.Exit(1) 262 } 263 hexcode = string(input) 264 } 265 266 hexcode = strings.TrimSpace(hexcode) 267 if len(hexcode)%2 != 0 { 268 fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode)) 269 os.Exit(1) 270 } 271 code = common.FromHex(hexcode) 272 273 runtimeConfig := runtime.Config{ 274 Origin: sender, 275 State: prestate, 276 GasLimit: initialGas, 277 GasPrice: flags.GlobalBig(ctx, PriceFlag.Name), 278 Value: flags.GlobalBig(ctx, ValueFlag.Name), 279 Difficulty: genesisConfig.Difficulty, 280 Time: genesisConfig.Timestamp, 281 Coinbase: genesisConfig.Coinbase, 282 BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), 283 BaseFee: genesisConfig.BaseFee, 284 BlobHashes: blobHashes, 285 BlobBaseFee: blobBaseFee, 286 EVMConfig: vm.Config{ 287 Tracer: tracer, 288 }, 289 } 290 291 if chainConfig != nil { 292 runtimeConfig.ChainConfig = chainConfig 293 } else { 294 runtimeConfig.ChainConfig = params.AllEthashProtocolChanges 295 } 296 297 var hexInput []byte 298 if inputFileFlag := ctx.String(InputFileFlag.Name); inputFileFlag != "" { 299 var err error 300 if hexInput, err = os.ReadFile(inputFileFlag); err != nil { 301 fmt.Printf("could not load input from file: %v\n", err) 302 os.Exit(1) 303 } 304 } else { 305 hexInput = []byte(ctx.String(InputFlag.Name)) 306 } 307 hexInput = bytes.TrimSpace(hexInput) 308 if len(hexInput)%2 != 0 { 309 fmt.Println("input length must be even") 310 os.Exit(1) 311 } 312 input := common.FromHex(string(hexInput)) 313 314 var execFunc func() ([]byte, uint64, error) 315 if ctx.Bool(CreateFlag.Name) { 316 input = append(code, input...) 317 execFunc = func() ([]byte, uint64, error) { 318 // don't mutate the state! 319 runtimeConfig.State = prestate.Copy() 320 output, _, gasLeft, err := runtime.Create(input, &runtimeConfig) 321 return output, gasLeft, err 322 } 323 } else { 324 if len(code) > 0 { 325 prestate.SetCode(receiver, code) 326 } 327 execFunc = func() ([]byte, uint64, error) { 328 // don't mutate the state! 329 runtimeConfig.State = prestate.Copy() 330 output, gasLeft, err := runtime.Call(receiver, input, &runtimeConfig) 331 return output, initialGas - gasLeft, err 332 } 333 } 334 335 bench := ctx.Bool(BenchFlag.Name) 336 output, stats, err := timedExec(bench, execFunc) 337 338 if ctx.Bool(DumpFlag.Name) { 339 root, err := runtimeConfig.State.Commit(genesisConfig.Number, true, false) 340 if err != nil { 341 fmt.Printf("Failed to commit changes %v\n", err) 342 return err 343 } 344 dumpdb, err := state.New(root, sdb) 345 if err != nil { 346 fmt.Printf("Failed to open statedb %v\n", err) 347 return err 348 } 349 fmt.Println(string(dumpdb.Dump(nil))) 350 } 351 352 if ctx.Bool(DebugFlag.Name) { 353 if logs := runtimeConfig.State.Logs(); len(logs) > 0 { 354 fmt.Fprintln(os.Stderr, "### LOGS") 355 writeLogs(os.Stderr, logs) 356 } 357 } 358 359 if bench || ctx.Bool(StatDumpFlag.Name) { 360 fmt.Fprintf(os.Stderr, `EVM gas used: %d 361 execution time: %v 362 allocations: %d 363 allocated bytes: %d 364 `, stats.GasUsed, stats.Time, stats.Allocs, stats.BytesAllocated) 365 } 366 if tracer == nil { 367 fmt.Printf("%#x\n", output) 368 if err != nil { 369 fmt.Printf(" error: %v\n", err) 370 } 371 } 372 373 return nil 374 } 375 376 // writeLogs writes vm logs in a readable format to the given writer 377 func writeLogs(writer io.Writer, logs []*types.Log) { 378 for _, log := range logs { 379 fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex) 380 381 for i, topic := range log.Topics { 382 fmt.Fprintf(writer, "%08d %x\n", i, topic) 383 } 384 fmt.Fprint(writer, hex.Dump(log.Data)) 385 fmt.Fprintln(writer) 386 } 387 }