github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/internal/tracetest/flat_calltrace_test.go (about)

     1  // Copyright 2023 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  	"reflect"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/ethereum/go-ethereum/common"
    30  	"github.com/ethereum/go-ethereum/common/hexutil"
    31  	"github.com/ethereum/go-ethereum/core"
    32  	"github.com/ethereum/go-ethereum/core/rawdb"
    33  	"github.com/ethereum/go-ethereum/core/types"
    34  	"github.com/ethereum/go-ethereum/core/vm"
    35  	"github.com/ethereum/go-ethereum/eth/tracers"
    36  	"github.com/ethereum/go-ethereum/rlp"
    37  	"github.com/ethereum/go-ethereum/tests"
    38  )
    39  
    40  // flatCallTrace is the result of a callTracerParity run.
    41  type flatCallTrace struct {
    42  	Action              flatCallTraceAction `json:"action"`
    43  	BlockHash           common.Hash         `json:"-"`
    44  	BlockNumber         uint64              `json:"-"`
    45  	Error               string              `json:"error,omitempty"`
    46  	Result              flatCallTraceResult `json:"result,omitempty"`
    47  	Subtraces           int                 `json:"subtraces"`
    48  	TraceAddress        []int               `json:"traceAddress"`
    49  	TransactionHash     common.Hash         `json:"-"`
    50  	TransactionPosition uint64              `json:"-"`
    51  	Type                string              `json:"type"`
    52  	Time                string              `json:"-"`
    53  }
    54  
    55  type flatCallTraceAction struct {
    56  	Author         common.Address `json:"author,omitempty"`
    57  	RewardType     string         `json:"rewardType,omitempty"`
    58  	SelfDestructed common.Address `json:"address,omitempty"`
    59  	Balance        hexutil.Big    `json:"balance,omitempty"`
    60  	CallType       string         `json:"callType,omitempty"`
    61  	CreationMethod string         `json:"creationMethod,omitempty"`
    62  	From           common.Address `json:"from,omitempty"`
    63  	Gas            hexutil.Uint64 `json:"gas,omitempty"`
    64  	Init           hexutil.Bytes  `json:"init,omitempty"`
    65  	Input          hexutil.Bytes  `json:"input,omitempty"`
    66  	RefundAddress  common.Address `json:"refundAddress,omitempty"`
    67  	To             common.Address `json:"to,omitempty"`
    68  	Value          hexutil.Big    `json:"value,omitempty"`
    69  }
    70  
    71  type flatCallTraceResult struct {
    72  	Address common.Address `json:"address,omitempty"`
    73  	Code    hexutil.Bytes  `json:"code,omitempty"`
    74  	GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"`
    75  	Output  hexutil.Bytes  `json:"output,omitempty"`
    76  }
    77  
    78  // flatCallTracerTest defines a single test to check the call tracer against.
    79  type flatCallTracerTest struct {
    80  	tracerTestEnv
    81  	Result []flatCallTrace `json:"result"`
    82  }
    83  
    84  func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error {
    85  	// Call tracer test found, read if from disk
    86  	blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename))
    87  	if err != nil {
    88  		return fmt.Errorf("failed to read testcase: %v", err)
    89  	}
    90  	test := new(flatCallTracerTest)
    91  	if err := json.Unmarshal(blob, test); err != nil {
    92  		return fmt.Errorf("failed to parse testcase: %v", err)
    93  	}
    94  	// Configure a blockchain with the given prestate
    95  	tx := new(types.Transaction)
    96  	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
    97  		return fmt.Errorf("failed to parse testcase input: %v", err)
    98  	}
    99  	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
   100  	context := test.Context.toBlockContext(test.Genesis)
   101  	state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme)
   102  	defer state.Close()
   103  
   104  	// Create the tracer, the EVM environment and run it
   105  	tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig, test.Genesis.Config)
   106  	if err != nil {
   107  		return fmt.Errorf("failed to create call tracer: %v", err)
   108  	}
   109  
   110  	msg, err := core.TransactionToMessage(tx, signer, context.BaseFee)
   111  	if err != nil {
   112  		return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
   113  	}
   114  	evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
   115  	tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
   116  	vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   117  	if err != nil {
   118  		return fmt.Errorf("failed to execute transaction: %v", err)
   119  	}
   120  	tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
   121  
   122  	// Retrieve the trace result and compare against the etalon
   123  	res, err := tracer.GetResult()
   124  	if err != nil {
   125  		return fmt.Errorf("failed to retrieve trace result: %v", err)
   126  	}
   127  	ret := make([]flatCallTrace, 0)
   128  	if err := json.Unmarshal(res, &ret); err != nil {
   129  		return fmt.Errorf("failed to unmarshal trace result: %v", err)
   130  	}
   131  	if !jsonEqualFlat(ret, test.Result) {
   132  		t.Logf("test %s failed", filename)
   133  
   134  		// uncomment this for easier debugging
   135  		// have, _ := json.MarshalIndent(ret, "", " ")
   136  		// want, _ := json.MarshalIndent(test.Result, "", " ")
   137  		// t.Logf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
   138  
   139  		// uncomment this for harder debugging <3 meowsbits
   140  		// lines := deep.Equal(ret, test.Result)
   141  		// for _, l := range lines {
   142  		// 	t.Logf("%s", l)
   143  		// 	t.FailNow()
   144  		// }
   145  
   146  		t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
   147  	}
   148  	return nil
   149  }
   150  
   151  // Iterates over all the input-output datasets in the tracer parity test harness and
   152  // runs the Native tracer against them.
   153  func TestFlatCallTracerNative(t *testing.T) {
   154  	testFlatCallTracer("flatCallTracer", "call_tracer_flat", t)
   155  }
   156  
   157  func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) {
   158  	files, err := os.ReadDir(filepath.Join("testdata", dirPath))
   159  	if err != nil {
   160  		t.Fatalf("failed to retrieve tracer test suite: %v", err)
   161  	}
   162  	for _, file := range files {
   163  		if !strings.HasSuffix(file.Name(), ".json") {
   164  			continue
   165  		}
   166  		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
   167  			t.Parallel()
   168  
   169  			err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t)
   170  			if err != nil {
   171  				t.Fatal(err)
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  // jsonEqualFlat is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
   178  // comparison
   179  func jsonEqualFlat(x, y interface{}) bool {
   180  	xTrace := new([]flatCallTrace)
   181  	yTrace := new([]flatCallTrace)
   182  	if xj, err := json.Marshal(x); err == nil {
   183  		json.Unmarshal(xj, xTrace)
   184  	} else {
   185  		return false
   186  	}
   187  	if yj, err := json.Marshal(y); err == nil {
   188  		json.Unmarshal(yj, yTrace)
   189  	} else {
   190  		return false
   191  	}
   192  	return reflect.DeepEqual(xTrace, yTrace)
   193  }
   194  
   195  func BenchmarkFlatCallTracer(b *testing.B) {
   196  	files, err := filepath.Glob("testdata/call_tracer_flat/*.json")
   197  	if err != nil {
   198  		b.Fatalf("failed to read testdata: %v", err)
   199  	}
   200  
   201  	for _, file := range files {
   202  		filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/")
   203  		b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) {
   204  			for n := 0; n < b.N; n++ {
   205  				err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b)
   206  				if err != nil {
   207  					b.Fatal(err)
   208  				}
   209  			}
   210  		})
   211  	}
   212  }