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

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