github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/eth/tracers/internal/tracetest/calltrace_test.go (about)

     1  // Copyright 2021 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser 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  // The go-ethereum library 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 Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package tracetest
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"math/big"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/common/hexutil"
    30  	"github.com/ethereum/go-ethereum/core"
    31  	"github.com/ethereum/go-ethereum/core/rawdb"
    32  	"github.com/ethereum/go-ethereum/core/types"
    33  	"github.com/ethereum/go-ethereum/core/vm"
    34  	"github.com/ethereum/go-ethereum/crypto"
    35  	"github.com/ethereum/go-ethereum/eth/tracers"
    36  	"github.com/ethereum/go-ethereum/params"
    37  	"github.com/ethereum/go-ethereum/rlp"
    38  	"github.com/ethereum/go-ethereum/tests"
    39  )
    40  
    41  // callLog is the result of LOG opCode
    42  type callLog struct {
    43  	Address  common.Address `json:"address"`
    44  	Topics   []common.Hash  `json:"topics"`
    45  	Data     hexutil.Bytes  `json:"data"`
    46  	Position hexutil.Uint   `json:"position"`
    47  }
    48  
    49  // callTrace is the result of a callTracer run.
    50  type callTrace struct {
    51  	From         common.Address  `json:"from"`
    52  	Gas          *hexutil.Uint64 `json:"gas"`
    53  	GasUsed      *hexutil.Uint64 `json:"gasUsed"`
    54  	To           *common.Address `json:"to,omitempty"`
    55  	Input        hexutil.Bytes   `json:"input"`
    56  	Output       hexutil.Bytes   `json:"output,omitempty"`
    57  	Error        string          `json:"error,omitempty"`
    58  	RevertReason string          `json:"revertReason,omitempty"`
    59  	Calls        []callTrace     `json:"calls,omitempty"`
    60  	Logs         []callLog       `json:"logs,omitempty"`
    61  	Value        *hexutil.Big    `json:"value,omitempty"`
    62  	// Gencodec adds overridden fields at the end
    63  	Type string `json:"type"`
    64  }
    65  
    66  // callTracerTest defines a single test to check the call tracer against.
    67  type callTracerTest struct {
    68  	Genesis      *core.Genesis   `json:"genesis"`
    69  	Context      *callContext    `json:"context"`
    70  	Input        string          `json:"input"`
    71  	TracerConfig json.RawMessage `json:"tracerConfig"`
    72  	Result       *callTrace      `json:"result"`
    73  }
    74  
    75  // Iterates over all the input-output datasets in the tracer test harness and
    76  // runs the JavaScript tracers against them.
    77  func TestCallTracerLegacy(t *testing.T) {
    78  	testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
    79  }
    80  
    81  func TestCallTracerNative(t *testing.T) {
    82  	testCallTracer("callTracer", "call_tracer", t)
    83  }
    84  
    85  func TestCallTracerNativeWithLog(t *testing.T) {
    86  	testCallTracer("callTracer", "call_tracer_withLog", t)
    87  }
    88  
    89  func testCallTracer(tracerName string, dirPath string, t *testing.T) {
    90  	isLegacy := strings.HasSuffix(dirPath, "_legacy")
    91  	files, err := os.ReadDir(filepath.Join("testdata", dirPath))
    92  	if err != nil {
    93  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
    94  	}
    95  	for _, file := range files {
    96  		if !strings.HasSuffix(file.Name(), ".json") {
    97  			continue
    98  		}
    99  		file := file // capture range variable
   100  		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
   101  			t.Parallel()
   102  
   103  			var (
   104  				test = new(callTracerTest)
   105  				tx   = new(types.Transaction)
   106  			)
   107  			// Call tracer test found, read if from disk
   108  			if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
   109  				t.Fatalf("failed to read testcase: %v", err)
   110  			} else if err := json.Unmarshal(blob, test); err != nil {
   111  				t.Fatalf("failed to parse testcase: %v", err)
   112  			}
   113  			if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil {
   114  				t.Fatalf("failed to parse testcase input: %v", err)
   115  			}
   116  			// Configure a blockchain with the given prestate
   117  			var (
   118  				signer  = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
   119  				context = test.Context.toBlockContext(test.Genesis)
   120  				state   = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
   121  			)
   122  			state.Close()
   123  
   124  			tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
   125  			if err != nil {
   126  				t.Fatalf("failed to create call tracer: %v", err)
   127  			}
   128  
   129  			state.StateDB.SetLogger(tracer.Hooks)
   130  			msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
   131  			if err != nil {
   132  				t.Fatalf("failed to prepare transaction for tracing: %v", err)
   133  			}
   134  			evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
   135  			tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
   136  			vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   137  			if err != nil {
   138  				t.Fatalf("failed to execute transaction: %v", err)
   139  			}
   140  			tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
   141  			// Retrieve the trace result and compare against the expected.
   142  			res, err := tracer.GetResult()
   143  			if err != nil {
   144  				t.Fatalf("failed to retrieve trace result: %v", err)
   145  			}
   146  			// The legacy javascript calltracer marshals json in js, which
   147  			// is not deterministic (as opposed to the golang json encoder).
   148  			if isLegacy {
   149  				// This is a tweak to make it deterministic. Can be removed when
   150  				// we remove the legacy tracer.
   151  				var x callTrace
   152  				json.Unmarshal(res, &x)
   153  				res, _ = json.Marshal(x)
   154  			}
   155  			want, err := json.Marshal(test.Result)
   156  			if err != nil {
   157  				t.Fatalf("failed to marshal test: %v", err)
   158  			}
   159  			if string(want) != string(res) {
   160  				t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want))
   161  			}
   162  			// Sanity check: compare top call's gas used against vm result
   163  			type simpleResult struct {
   164  				GasUsed hexutil.Uint64
   165  			}
   166  			var topCall simpleResult
   167  			if err := json.Unmarshal(res, &topCall); err != nil {
   168  				t.Fatalf("failed to unmarshal top calls gasUsed: %v", err)
   169  			}
   170  			if uint64(topCall.GasUsed) != vmRet.UsedGas {
   171  				t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas)
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func BenchmarkTracers(b *testing.B) {
   178  	files, err := os.ReadDir(filepath.Join("testdata", "call_tracer"))
   179  	if err != nil {
   180  		b.Fatalf("failed to retrieve tracer test suite: %v", err)
   181  	}
   182  	for _, file := range files {
   183  		if !strings.HasSuffix(file.Name(), ".json") {
   184  			continue
   185  		}
   186  		file := file // capture range variable
   187  		b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
   188  			blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
   189  			if err != nil {
   190  				b.Fatalf("failed to read testcase: %v", err)
   191  			}
   192  			test := new(callTracerTest)
   193  			if err := json.Unmarshal(blob, test); err != nil {
   194  				b.Fatalf("failed to parse testcase: %v", err)
   195  			}
   196  			benchTracer("callTracer", test, b)
   197  		})
   198  	}
   199  }
   200  
   201  func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
   202  	// Configure a blockchain with the given prestate
   203  	tx := new(types.Transaction)
   204  	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
   205  		b.Fatalf("failed to parse testcase input: %v", err)
   206  	}
   207  	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
   208  	origin, _ := signer.Sender(tx)
   209  	txContext := vm.TxContext{
   210  		Origin:   origin,
   211  		GasPrice: tx.GasPrice(),
   212  	}
   213  	context := vm.BlockContext{
   214  		CanTransfer: core.CanTransfer,
   215  		Transfer:    core.Transfer,
   216  		Coinbase:    test.Context.Miner,
   217  		BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
   218  		Time:        uint64(test.Context.Time),
   219  		Difficulty:  (*big.Int)(test.Context.Difficulty),
   220  		GasLimit:    uint64(test.Context.GasLimit),
   221  	}
   222  	msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
   223  	if err != nil {
   224  		b.Fatalf("failed to prepare transaction for tracing: %v", err)
   225  	}
   226  	state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
   227  	defer state.Close()
   228  
   229  	b.ReportAllocs()
   230  	b.ResetTimer()
   231  	for i := 0; i < b.N; i++ {
   232  		tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil)
   233  		if err != nil {
   234  			b.Fatalf("failed to create call tracer: %v", err)
   235  		}
   236  		evm := vm.NewEVM(context, txContext, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
   237  		snap := state.StateDB.Snapshot()
   238  		st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   239  		if _, err = st.TransitionDb(); err != nil {
   240  			b.Fatalf("failed to execute transaction: %v", err)
   241  		}
   242  		if _, err = tracer.GetResult(); err != nil {
   243  			b.Fatal(err)
   244  		}
   245  		state.StateDB.RevertToSnapshot(snap)
   246  	}
   247  }
   248  
   249  func TestInternals(t *testing.T) {
   250  	var (
   251  		config    = params.MainnetChainConfig
   252  		to        = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
   253  		originHex = "0x71562b71999873db5b286df957af199ec94617f7"
   254  		origin    = common.HexToAddress(originHex)
   255  		signer    = types.LatestSigner(config)
   256  		key, _    = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
   257  		context   = vm.BlockContext{
   258  			CanTransfer: core.CanTransfer,
   259  			Transfer:    core.Transfer,
   260  			Coinbase:    common.Address{},
   261  			BlockNumber: new(big.Int).SetUint64(8000000),
   262  			Time:        5,
   263  			Difficulty:  big.NewInt(0x30000),
   264  			GasLimit:    uint64(6000000),
   265  			BaseFee:     new(big.Int),
   266  		}
   267  	)
   268  	mkTracer := func(name string, cfg json.RawMessage) *tracers.Tracer {
   269  		tr, err := tracers.DefaultDirectory.New(name, nil, cfg)
   270  		if err != nil {
   271  			t.Fatalf("failed to create call tracer: %v", err)
   272  		}
   273  		return tr
   274  	}
   275  
   276  	for _, tc := range []struct {
   277  		name   string
   278  		code   []byte
   279  		tracer *tracers.Tracer
   280  		want   string
   281  	}{
   282  		{
   283  			// TestZeroValueToNotExitCall tests the calltracer(s) on the following:
   284  			// Tx to A, A calls B with zero value. B does not already exist.
   285  			// Expected: that enter/exit is invoked and the inner call is shown in the result
   286  			name: "ZeroValueToNotExitCall",
   287  			code: []byte{
   288  				byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
   289  				byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
   290  				byte(vm.CALL),
   291  			},
   292  			tracer: mkTracer("callTracer", nil),
   293  			want:   fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex),
   294  		},
   295  		{
   296  			name:   "Stack depletion in LOG0",
   297  			code:   []byte{byte(vm.LOG3)},
   298  			tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
   299  			want:   fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex),
   300  		},
   301  		{
   302  			name: "Mem expansion in LOG0",
   303  			code: []byte{
   304  				byte(vm.PUSH1), 0x1,
   305  				byte(vm.PUSH1), 0x0,
   306  				byte(vm.MSTORE),
   307  				byte(vm.PUSH1), 0xff,
   308  				byte(vm.PUSH1), 0x0,
   309  				byte(vm.LOG0),
   310  			},
   311  			tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
   312  			want:   fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
   313  		},
   314  		{
   315  			// Leads to OOM on the prestate tracer
   316  			name: "Prestate-tracer - CREATE2 OOM",
   317  			code: []byte{
   318  				byte(vm.PUSH1), 0x1,
   319  				byte(vm.PUSH1), 0x0,
   320  				byte(vm.MSTORE),
   321  				byte(vm.PUSH1), 0x1,
   322  				byte(vm.PUSH5), 0xff, 0xff, 0xff, 0xff, 0xff,
   323  				byte(vm.PUSH1), 0x1,
   324  				byte(vm.PUSH1), 0x0,
   325  				byte(vm.CREATE2),
   326  				byte(vm.PUSH1), 0xff,
   327  				byte(vm.PUSH1), 0x0,
   328  				byte(vm.LOG0),
   329  			},
   330  			tracer: mkTracer("prestateTracer", nil),
   331  			want:   fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
   332  		},
   333  		{
   334  			// CREATE2 which requires padding memory by prestate tracer
   335  			name: "Prestate-tracer - CREATE2 Memory padding",
   336  			code: []byte{
   337  				byte(vm.PUSH1), 0x1,
   338  				byte(vm.PUSH1), 0x0,
   339  				byte(vm.MSTORE),
   340  				byte(vm.PUSH1), 0x1,
   341  				byte(vm.PUSH1), 0xff,
   342  				byte(vm.PUSH1), 0x1,
   343  				byte(vm.PUSH1), 0x0,
   344  				byte(vm.CREATE2),
   345  				byte(vm.PUSH1), 0xff,
   346  				byte(vm.PUSH1), 0x0,
   347  				byte(vm.LOG0),
   348  			},
   349  			tracer: mkTracer("prestateTracer", nil),
   350  			want:   fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
   351  		},
   352  	} {
   353  		t.Run(tc.name, func(t *testing.T) {
   354  			state := tests.MakePreState(rawdb.NewMemoryDatabase(),
   355  				types.GenesisAlloc{
   356  					to: types.Account{
   357  						Code: tc.code,
   358  					},
   359  					origin: types.Account{
   360  						Balance: big.NewInt(500000000000000),
   361  					},
   362  				}, false, rawdb.HashScheme)
   363  			defer state.Close()
   364  			state.StateDB.SetLogger(tc.tracer.Hooks)
   365  			tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
   366  				To:       &to,
   367  				Value:    big.NewInt(0),
   368  				Gas:      80000,
   369  				GasPrice: big.NewInt(1),
   370  			})
   371  			if err != nil {
   372  				t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
   373  			}
   374  			txContext := vm.TxContext{
   375  				Origin:   origin,
   376  				GasPrice: tx.GasPrice(),
   377  			}
   378  			evm := vm.NewEVM(context, txContext, state.StateDB, config, vm.Config{Tracer: tc.tracer.Hooks})
   379  			msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0))
   380  			if err != nil {
   381  				t.Fatalf("test %v: failed to create message: %v", tc.name, err)
   382  			}
   383  			tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
   384  			vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   385  			if err != nil {
   386  				t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
   387  			}
   388  			tc.tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
   389  			// Retrieve the trace result and compare against the expected
   390  			res, err := tc.tracer.GetResult()
   391  			if err != nil {
   392  				t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err)
   393  			}
   394  			if string(res) != tc.want {
   395  				t.Errorf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want)
   396  			}
   397  		})
   398  	}
   399  }