github.com/calmw/ethereum@v0.1.1/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/calmw/ethereum/common"
    14  	"github.com/calmw/ethereum/common/hexutil"
    15  	"github.com/calmw/ethereum/core"
    16  	"github.com/calmw/ethereum/core/rawdb"
    17  	"github.com/calmw/ethereum/core/types"
    18  	"github.com/calmw/ethereum/core/vm"
    19  	"github.com/calmw/ethereum/rlp"
    20  	"github.com/calmw/ethereum/tests"
    21  
    22  	// Force-load the native, to trigger registration
    23  	"github.com/calmw/ethereum/eth/tracers"
    24  )
    25  
    26  // flatCallTrace is the result of a callTracerParity run.
    27  type flatCallTrace struct {
    28  	Action              flatCallTraceAction `json:"action"`
    29  	BlockHash           common.Hash         `json:"-"`
    30  	BlockNumber         uint64              `json:"-"`
    31  	Error               string              `json:"error,omitempty"`
    32  	Result              flatCallTraceResult `json:"result,omitempty"`
    33  	Subtraces           int                 `json:"subtraces"`
    34  	TraceAddress        []int               `json:"traceAddress"`
    35  	TransactionHash     common.Hash         `json:"-"`
    36  	TransactionPosition uint64              `json:"-"`
    37  	Type                string              `json:"type"`
    38  	Time                string              `json:"-"`
    39  }
    40  
    41  type flatCallTraceAction struct {
    42  	Author         common.Address `json:"author,omitempty"`
    43  	RewardType     string         `json:"rewardType,omitempty"`
    44  	SelfDestructed common.Address `json:"address,omitempty"`
    45  	Balance        hexutil.Big    `json:"balance,omitempty"`
    46  	CallType       string         `json:"callType,omitempty"`
    47  	CreationMethod string         `json:"creationMethod,omitempty"`
    48  	From           common.Address `json:"from,omitempty"`
    49  	Gas            hexutil.Uint64 `json:"gas,omitempty"`
    50  	Init           hexutil.Bytes  `json:"init,omitempty"`
    51  	Input          hexutil.Bytes  `json:"input,omitempty"`
    52  	RefundAddress  common.Address `json:"refundAddress,omitempty"`
    53  	To             common.Address `json:"to,omitempty"`
    54  	Value          hexutil.Big    `json:"value,omitempty"`
    55  }
    56  
    57  type flatCallTraceResult struct {
    58  	Address common.Address `json:"address,omitempty"`
    59  	Code    hexutil.Bytes  `json:"code,omitempty"`
    60  	GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"`
    61  	Output  hexutil.Bytes  `json:"output,omitempty"`
    62  }
    63  
    64  // flatCallTracerTest defines a single test to check the call tracer against.
    65  type flatCallTracerTest struct {
    66  	Genesis      core.Genesis    `json:"genesis"`
    67  	Context      callContext     `json:"context"`
    68  	Input        string          `json:"input"`
    69  	TracerConfig json.RawMessage `json:"tracerConfig"`
    70  	Result       []flatCallTrace `json:"result"`
    71  }
    72  
    73  func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error {
    74  	// Call tracer test found, read if from disk
    75  	blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename))
    76  	if err != nil {
    77  		return fmt.Errorf("failed to read testcase: %v", err)
    78  	}
    79  	test := new(flatCallTracerTest)
    80  	if err := json.Unmarshal(blob, test); err != nil {
    81  		return fmt.Errorf("failed to parse testcase: %v", err)
    82  	}
    83  	// Configure a blockchain with the given prestate
    84  	tx := new(types.Transaction)
    85  	if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
    86  		return fmt.Errorf("failed to parse testcase input: %v", err)
    87  	}
    88  	signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time))
    89  	origin, _ := signer.Sender(tx)
    90  	txContext := vm.TxContext{
    91  		Origin:   origin,
    92  		GasPrice: tx.GasPrice(),
    93  	}
    94  	context := vm.BlockContext{
    95  		CanTransfer: core.CanTransfer,
    96  		Transfer:    core.Transfer,
    97  		Coinbase:    test.Context.Miner,
    98  		BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
    99  		Time:        uint64(test.Context.Time),
   100  		Difficulty:  (*big.Int)(test.Context.Difficulty),
   101  		GasLimit:    uint64(test.Context.GasLimit),
   102  	}
   103  	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
   104  
   105  	// Create the tracer, the EVM environment and run it
   106  	tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
   107  	if err != nil {
   108  		return fmt.Errorf("failed to create call tracer: %v", err)
   109  	}
   110  	evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
   111  
   112  	msg, err := core.TransactionToMessage(tx, signer, nil)
   113  	if err != nil {
   114  		return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
   115  	}
   116  	st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
   117  
   118  	if _, err = st.TransitionDb(); err != nil {
   119  		return fmt.Errorf("failed to execute transaction: %v", err)
   120  	}
   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("tracer name: %s", tracerName)
   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  		file := file // capture range variable
   167  		t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
   168  			t.Parallel()
   169  
   170  			err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t)
   171  			if err != nil {
   172  				t.Fatal(err)
   173  			}
   174  		})
   175  	}
   176  }
   177  
   178  // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
   179  // comparison
   180  func jsonEqualFlat(x, y interface{}) bool {
   181  	xTrace := new([]flatCallTrace)
   182  	yTrace := new([]flatCallTrace)
   183  	if xj, err := json.Marshal(x); err == nil {
   184  		json.Unmarshal(xj, xTrace)
   185  	} else {
   186  		return false
   187  	}
   188  	if yj, err := json.Marshal(y); err == nil {
   189  		json.Unmarshal(yj, yTrace)
   190  	} else {
   191  		return false
   192  	}
   193  	return reflect.DeepEqual(xTrace, yTrace)
   194  }
   195  
   196  func BenchmarkFlatCallTracer(b *testing.B) {
   197  	files, err := filepath.Glob("testdata/call_tracer_flat/*.json")
   198  	if err != nil {
   199  		b.Fatalf("failed to read testdata: %v", err)
   200  	}
   201  
   202  	for _, file := range files {
   203  		filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/")
   204  		b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) {
   205  			for n := 0; n < b.N; n++ {
   206  				err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b)
   207  				if err != nil {
   208  					b.Fatal(err)
   209  				}
   210  			}
   211  		})
   212  	}
   213  }