123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- // Copyright 2017 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- package tracers
- import (
- "encoding/json"
- "io/ioutil"
- "math/big"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/core/vm"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/tests"
- )
- // To generate a new callTracer test, copy paste the makeTest method below into
- // a Geth console and call it with a transaction hash you which to export.
- /*
- // makeTest generates a callTracer test by running a prestate reassembled and a
- // call trace run, assembling all the gathered information into a test case.
- var makeTest = function(tx, rewind) {
- // Generate the genesis block from the block, transaction and prestate data
- var block = eth.getBlock(eth.getTransaction(tx).blockHash);
- var genesis = eth.getBlock(block.parentHash);
- delete genesis.gasUsed;
- delete genesis.logsBloom;
- delete genesis.parentHash;
- delete genesis.receiptsRoot;
- delete genesis.sha3Uncles;
- delete genesis.size;
- delete genesis.transactions;
- delete genesis.transactionsRoot;
- delete genesis.uncles;
- genesis.gasLimit = genesis.gasLimit.toString();
- genesis.number = genesis.number.toString();
- genesis.timestamp = genesis.timestamp.toString();
- genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
- for (var key in genesis.alloc) {
- genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
- }
- genesis.config = admin.nodeInfo.protocols.eth.config;
- // Generate the call trace and produce the test input
- var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
- delete result.time;
- console.log(JSON.stringify({
- genesis: genesis,
- context: {
- number: block.number.toString(),
- difficulty: block.difficulty,
- timestamp: block.timestamp.toString(),
- gasLimit: block.gasLimit.toString(),
- miner: block.miner,
- },
- input: eth.getRawTransaction(tx),
- result: result,
- }, null, 2));
- }
- */
- // callTrace is the result of a callTracer run.
- type callTrace struct {
- Type string `json:"type"`
- From common.Address `json:"from"`
- To common.Address `json:"to"`
- Input hexutil.Bytes `json:"input"`
- Output hexutil.Bytes `json:"output"`
- Gas *hexutil.Uint64 `json:"gas,omitempty"`
- GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
- Value *hexutil.Big `json:"value,omitempty"`
- Error string `json:"error,omitempty"`
- Calls []callTrace `json:"calls,omitempty"`
- }
- type callContext struct {
- Number math.HexOrDecimal64 `json:"number"`
- Difficulty *math.HexOrDecimal256 `json:"difficulty"`
- Time math.HexOrDecimal64 `json:"timestamp"`
- GasLimit math.HexOrDecimal64 `json:"gasLimit"`
- Miner common.Address `json:"miner"`
- }
- // callTracerTest defines a single test to check the call tracer against.
- type callTracerTest struct {
- Genesis *core.Genesis `json:"genesis"`
- Context *callContext `json:"context"`
- Input string `json:"input"`
- Result *callTrace `json:"result"`
- }
- // Iterates over all the input-output datasets in the tracer test harness and
- // runs the JavaScript tracers against them.
- func TestCallTracer(t *testing.T) {
- files, err := ioutil.ReadDir("testdata")
- if err != nil {
- t.Fatalf("failed to retrieve tracer test suite: %v", err)
- }
- for _, file := range files {
- if !strings.HasPrefix(file.Name(), "call_tracer_") {
- continue
- }
- file := file // capture range variable
- t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) {
- t.Parallel()
- // Call tracer test found, read if from disk
- blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name()))
- if err != nil {
- t.Fatalf("failed to read testcase: %v", err)
- }
- test := new(callTracerTest)
- if err := json.Unmarshal(blob, test); err != nil {
- t.Fatalf("failed to parse testcase: %v", err)
- }
- // Configure a blockchain with the given prestate
- tx := new(types.Transaction)
- if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
- t.Fatalf("failed to parse testcase input: %v", err)
- }
- signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
- origin, _ := signer.Sender(tx)
- context := vm.Context{
- CanTransfer: core.CanTransfer,
- Transfer: core.Transfer,
- Origin: origin,
- Coinbase: test.Context.Miner,
- BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
- Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
- Difficulty: (*big.Int)(test.Context.Difficulty),
- GasLimit: uint64(test.Context.GasLimit),
- GasPrice: tx.GasPrice(),
- }
- statedb := tests.MakePreState(ethdb.NewMemDatabase(), test.Genesis.Alloc)
- // Create the tracer, the EVM environment and run it
- tracer, err := New("callTracer")
- if err != nil {
- t.Fatalf("failed to create call tracer: %v", err)
- }
- evm := vm.NewEVM(context, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
- msg, err := tx.AsMessage(signer)
- if err != nil {
- t.Fatalf("failed to prepare transaction for tracing: %v", err)
- }
- st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
- if _, _, _, err = st.TransitionDb(); err != nil {
- t.Fatalf("failed to execute transaction: %v", err)
- }
- // Retrieve the trace result and compare against the etalon
- res, err := tracer.GetResult()
- if err != nil {
- t.Fatalf("failed to retrieve trace result: %v", err)
- }
- ret := new(callTrace)
- if err := json.Unmarshal(res, ret); err != nil {
- t.Fatalf("failed to unmarshal trace result: %v", err)
- }
- if !reflect.DeepEqual(ret, test.Result) {
- t.Fatalf("trace mismatch: have %+v, want %+v", ret, test.Result)
- }
- })
- }
- }
|