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  }