github.com/ethereum-optimism/optimism/l2geth@v0.0.0-20230612200230-50b04ade19e3/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/json"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"math/big"
    25  	"os"
    26  	goruntime "runtime"
    27  	"runtime/pprof"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/ethereum-optimism/optimism/l2geth/cmd/evm/internal/compiler"
    32  	"github.com/ethereum-optimism/optimism/l2geth/cmd/utils"
    33  	"github.com/ethereum-optimism/optimism/l2geth/common"
    34  	"github.com/ethereum-optimism/optimism/l2geth/core"
    35  	"github.com/ethereum-optimism/optimism/l2geth/core/rawdb"
    36  	"github.com/ethereum-optimism/optimism/l2geth/core/state"
    37  	"github.com/ethereum-optimism/optimism/l2geth/core/vm"
    38  	"github.com/ethereum-optimism/optimism/l2geth/core/vm/runtime"
    39  	"github.com/ethereum-optimism/optimism/l2geth/log"
    40  	"github.com/ethereum-optimism/optimism/l2geth/params"
    41  	cli "gopkg.in/urfave/cli.v1"
    42  )
    43  
    44  var runCommand = cli.Command{
    45  	Action:      runCmd,
    46  	Name:        "run",
    47  	Usage:       "run arbitrary evm binary",
    48  	ArgsUsage:   "<code>",
    49  	Description: `The run command runs arbitrary EVM code.`,
    50  }
    51  
    52  // readGenesis will read the given JSON format genesis file and return
    53  // the initialized Genesis structure
    54  func readGenesis(genesisPath string) *core.Genesis {
    55  	// Make sure we have a valid genesis JSON
    56  	//genesisPath := ctx.Args().First()
    57  	if len(genesisPath) == 0 {
    58  		utils.Fatalf("Must supply path to genesis JSON file")
    59  	}
    60  	file, err := os.Open(genesisPath)
    61  	if err != nil {
    62  		utils.Fatalf("Failed to read genesis file: %v", err)
    63  	}
    64  	defer file.Close()
    65  
    66  	genesis := new(core.Genesis)
    67  	if err := json.NewDecoder(file).Decode(genesis); err != nil {
    68  		utils.Fatalf("invalid genesis file: %v", err)
    69  	}
    70  	return genesis
    71  }
    72  
    73  func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, uint64, time.Duration, error) {
    74  	var (
    75  		output   []byte
    76  		gasLeft  uint64
    77  		execTime time.Duration
    78  		err      error
    79  	)
    80  
    81  	if bench {
    82  		result := testing.Benchmark(func(b *testing.B) {
    83  			for i := 0; i < b.N; i++ {
    84  				output, gasLeft, err = execFunc()
    85  			}
    86  		})
    87  
    88  		// Get the average execution time from the benchmarking result.
    89  		// There are other useful stats here that could be reported.
    90  		execTime = time.Duration(result.NsPerOp())
    91  	} else {
    92  		startTime := time.Now()
    93  		output, gasLeft, err = execFunc()
    94  		execTime = time.Since(startTime)
    95  	}
    96  
    97  	return output, gasLeft, execTime, err
    98  }
    99  
   100  func runCmd(ctx *cli.Context) error {
   101  	glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
   102  	glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
   103  	log.Root().SetHandler(glogger)
   104  	logconfig := &vm.LogConfig{
   105  		DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
   106  		DisableStack:  ctx.GlobalBool(DisableStackFlag.Name),
   107  		Debug:         ctx.GlobalBool(DebugFlag.Name),
   108  	}
   109  
   110  	var (
   111  		tracer        vm.Tracer
   112  		debugLogger   *vm.StructLogger
   113  		statedb       *state.StateDB
   114  		chainConfig   *params.ChainConfig
   115  		sender        = common.BytesToAddress([]byte("sender"))
   116  		receiver      = common.BytesToAddress([]byte("receiver"))
   117  		genesisConfig *core.Genesis
   118  	)
   119  	if ctx.GlobalBool(MachineFlag.Name) {
   120  		tracer = vm.NewJSONLogger(logconfig, os.Stdout)
   121  	} else if ctx.GlobalBool(DebugFlag.Name) {
   122  		debugLogger = vm.NewStructLogger(logconfig)
   123  		tracer = debugLogger
   124  	} else {
   125  		debugLogger = vm.NewStructLogger(logconfig)
   126  	}
   127  	if ctx.GlobalString(GenesisFlag.Name) != "" {
   128  		gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
   129  		genesisConfig = gen
   130  		db := rawdb.NewMemoryDatabase()
   131  		genesis := gen.ToBlock(db)
   132  		statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
   133  		chainConfig = gen.Config
   134  	} else {
   135  		statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
   136  		genesisConfig = new(core.Genesis)
   137  	}
   138  	if ctx.GlobalString(SenderFlag.Name) != "" {
   139  		sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))
   140  	}
   141  	statedb.CreateAccount(sender)
   142  
   143  	if ctx.GlobalString(ReceiverFlag.Name) != "" {
   144  		receiver = common.HexToAddress(ctx.GlobalString(ReceiverFlag.Name))
   145  	}
   146  
   147  	var code []byte
   148  	codeFileFlag := ctx.GlobalString(CodeFileFlag.Name)
   149  	codeFlag := ctx.GlobalString(CodeFlag.Name)
   150  
   151  	// The '--code' or '--codefile' flag overrides code in state
   152  	if codeFileFlag != "" || codeFlag != "" {
   153  		var hexcode []byte
   154  		if codeFileFlag != "" {
   155  			var err error
   156  			// If - is specified, it means that code comes from stdin
   157  			if codeFileFlag == "-" {
   158  				//Try reading from stdin
   159  				if hexcode, err = ioutil.ReadAll(os.Stdin); err != nil {
   160  					fmt.Printf("Could not load code from stdin: %v\n", err)
   161  					os.Exit(1)
   162  				}
   163  			} else {
   164  				// Codefile with hex assembly
   165  				if hexcode, err = ioutil.ReadFile(codeFileFlag); err != nil {
   166  					fmt.Printf("Could not load code from file: %v\n", err)
   167  					os.Exit(1)
   168  				}
   169  			}
   170  		} else {
   171  			hexcode = []byte(codeFlag)
   172  		}
   173  		hexcode = bytes.TrimSpace(hexcode)
   174  		if len(hexcode)%2 != 0 {
   175  			fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode))
   176  			os.Exit(1)
   177  		}
   178  		code = common.FromHex(string(hexcode))
   179  	} else if fn := ctx.Args().First(); len(fn) > 0 {
   180  		// EASM-file to compile
   181  		src, err := ioutil.ReadFile(fn)
   182  		if err != nil {
   183  			return err
   184  		}
   185  		bin, err := compiler.Compile(fn, src, false)
   186  		if err != nil {
   187  			return err
   188  		}
   189  		code = common.Hex2Bytes(bin)
   190  	}
   191  	initialGas := ctx.GlobalUint64(GasFlag.Name)
   192  	if genesisConfig.GasLimit != 0 {
   193  		initialGas = genesisConfig.GasLimit
   194  	}
   195  	runtimeConfig := runtime.Config{
   196  		Origin:      sender,
   197  		State:       statedb,
   198  		GasLimit:    initialGas,
   199  		GasPrice:    utils.GlobalBig(ctx, PriceFlag.Name),
   200  		Value:       utils.GlobalBig(ctx, ValueFlag.Name),
   201  		Difficulty:  genesisConfig.Difficulty,
   202  		Time:        new(big.Int).SetUint64(genesisConfig.Timestamp),
   203  		Coinbase:    genesisConfig.Coinbase,
   204  		BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
   205  		EVMConfig: vm.Config{
   206  			Tracer:         tracer,
   207  			Debug:          ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name),
   208  			EVMInterpreter: ctx.GlobalString(EVMInterpreterFlag.Name),
   209  		},
   210  	}
   211  
   212  	if cpuProfilePath := ctx.GlobalString(CPUProfileFlag.Name); cpuProfilePath != "" {
   213  		f, err := os.Create(cpuProfilePath)
   214  		if err != nil {
   215  			fmt.Println("could not create CPU profile: ", err)
   216  			os.Exit(1)
   217  		}
   218  		if err := pprof.StartCPUProfile(f); err != nil {
   219  			fmt.Println("could not start CPU profile: ", err)
   220  			os.Exit(1)
   221  		}
   222  		defer pprof.StopCPUProfile()
   223  	}
   224  
   225  	if chainConfig != nil {
   226  		runtimeConfig.ChainConfig = chainConfig
   227  	} else {
   228  		runtimeConfig.ChainConfig = params.AllEthashProtocolChanges
   229  	}
   230  
   231  	var hexInput []byte
   232  	if inputFileFlag := ctx.GlobalString(InputFileFlag.Name); inputFileFlag != "" {
   233  		var err error
   234  		if hexInput, err = ioutil.ReadFile(inputFileFlag); err != nil {
   235  			fmt.Printf("could not load input from file: %v\n", err)
   236  			os.Exit(1)
   237  		}
   238  	} else {
   239  		hexInput = []byte(ctx.GlobalString(InputFlag.Name))
   240  	}
   241  	input := common.FromHex(string(bytes.TrimSpace(hexInput)))
   242  
   243  	var execFunc func() ([]byte, uint64, error)
   244  	if ctx.GlobalBool(CreateFlag.Name) {
   245  		input = append(code, input...)
   246  		execFunc = func() ([]byte, uint64, error) {
   247  			output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
   248  			return output, gasLeft, err
   249  		}
   250  	} else {
   251  		if len(code) > 0 {
   252  			statedb.SetCode(receiver, code)
   253  		}
   254  		execFunc = func() ([]byte, uint64, error) {
   255  			return runtime.Call(receiver, input, &runtimeConfig)
   256  		}
   257  	}
   258  
   259  	output, leftOverGas, execTime, err := timedExec(ctx.GlobalBool(BenchFlag.Name), execFunc)
   260  
   261  	if ctx.GlobalBool(DumpFlag.Name) {
   262  		statedb.Commit(true)
   263  		statedb.IntermediateRoot(true)
   264  		fmt.Println(string(statedb.Dump(false, false, true)))
   265  	}
   266  
   267  	if memProfilePath := ctx.GlobalString(MemProfileFlag.Name); memProfilePath != "" {
   268  		f, err := os.Create(memProfilePath)
   269  		if err != nil {
   270  			fmt.Println("could not create memory profile: ", err)
   271  			os.Exit(1)
   272  		}
   273  		if err := pprof.WriteHeapProfile(f); err != nil {
   274  			fmt.Println("could not write memory profile: ", err)
   275  			os.Exit(1)
   276  		}
   277  		f.Close()
   278  	}
   279  
   280  	if ctx.GlobalBool(DebugFlag.Name) {
   281  		if debugLogger != nil {
   282  			fmt.Fprintln(os.Stderr, "#### TRACE ####")
   283  			vm.WriteTrace(os.Stderr, debugLogger.StructLogs())
   284  		}
   285  		fmt.Fprintln(os.Stderr, "#### LOGS ####")
   286  		vm.WriteLogs(os.Stderr, statedb.Logs())
   287  	}
   288  
   289  	if ctx.GlobalBool(StatDumpFlag.Name) {
   290  		var mem goruntime.MemStats
   291  		goruntime.ReadMemStats(&mem)
   292  		fmt.Fprintf(os.Stderr, `evm execution time: %v
   293  heap objects:       %d
   294  allocations:        %d
   295  total allocations:  %d
   296  GC calls:           %d
   297  Gas used:           %d
   298  
   299  `, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
   300  	}
   301  	if tracer == nil {
   302  		fmt.Printf("0x%x\n", output)
   303  		if err != nil {
   304  			fmt.Printf(" error: %v\n", err)
   305  		}
   306  	}
   307  
   308  	return nil
   309  }