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