Unverified Commit 5d52a359 authored by Delweng's avatar Delweng Committed by GitHub

eth/tracers: add diffMode to prestateTracer (#25422)

Backwards compatibility warning: The result will from now on omit empty fields instead
of including a zero value (e.g. no more `balance: '0x'`).

The prestateTracer will now take an option `diffMode: bool`. In this mode
the tracer will output the pre state and post data for the modified parts of state.
Read-only accesses will be completely omitted. Creations (be it account or slot)
will be signified by omission in the `pre` list and inclusion in `post`. Whereas
deletion (be it account or slot) will be signified by inclusion in `pre` and omission
in `post` list.
Signed-off-by: 's avatarDelweng <delweng@gmail.com>
parent e14164d5
...@@ -21,10 +21,8 @@ import ( ...@@ -21,10 +21,8 @@ import (
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
"unicode"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -38,62 +36,8 @@ import ( ...@@ -38,62 +36,8 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
// Force-load native and js packages, to trigger registration
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
) )
// 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));
}
*/
type callContext struct { type callContext struct {
Number math.HexOrDecimal64 `json:"number"` Number math.HexOrDecimal64 `json:"number"`
Difficulty *math.HexOrDecimal256 `json:"difficulty"` Difficulty *math.HexOrDecimal256 `json:"difficulty"`
...@@ -204,7 +148,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { ...@@ -204,7 +148,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
t.Fatalf("failed to unmarshal trace result: %v", err) t.Fatalf("failed to unmarshal trace result: %v", err)
} }
if !jsonEqual(ret, test.Result) { if !jsonEqual(ret, test.Result, new(callTrace), new(callTrace)) {
// uncomment this for easier debugging // uncomment this for easier debugging
//have, _ := json.MarshalIndent(ret, "", " ") //have, _ := json.MarshalIndent(ret, "", " ")
//want, _ := json.MarshalIndent(test.Result, "", " ") //want, _ := json.MarshalIndent(test.Result, "", " ")
...@@ -215,32 +159,6 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { ...@@ -215,32 +159,6 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
} }
} }
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
// comparison
func jsonEqual(x, y interface{}) bool {
xTrace := new(callTrace)
yTrace := new(callTrace)
if xj, err := json.Marshal(x); err == nil {
json.Unmarshal(xj, xTrace)
} else {
return false
}
if yj, err := json.Marshal(y); err == nil {
json.Unmarshal(yj, yTrace)
} else {
return false
}
return reflect.DeepEqual(xTrace, yTrace)
}
// camel converts a snake cased input string into a camel cased output.
func camel(str string) string {
pieces := strings.Split(str, "_")
for i := 1; i < len(pieces); i++ {
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
}
return strings.Join(pieces, "")
}
func BenchmarkTracers(b *testing.B) { func BenchmarkTracers(b *testing.B) {
files, err := os.ReadDir(filepath.Join("testdata", "call_tracer")) files, err := os.ReadDir(filepath.Join("testdata", "call_tracer"))
if err != nil { if err != nil {
...@@ -386,7 +304,7 @@ func TestZeroValueToNotExitCall(t *testing.T) { ...@@ -386,7 +304,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}` wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
want := new(callTrace) want := new(callTrace)
json.Unmarshal([]byte(wantStr), want) json.Unmarshal([]byte(wantStr), want)
if !jsonEqual(have, want) { if !jsonEqual(have, want, new(callTrace), new(callTrace)) {
t.Error("have != want") t.Error("have != want")
} }
} }
// Copyright 2021 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 tracetest
import (
"encoding/json"
"math/big"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests"
)
// prestateTrace is the result of a prestateTrace run.
type prestateTrace = map[common.Address]*account
type account struct {
Balance string `json:"balance,omitempty"`
Nonce uint64 `json:"nonce,omitempty"`
Code string `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
}
type prePostStateTrace struct {
Pre prestateTrace `json:"pre"`
Post prestateTrace `json:"post"`
}
// prestateTraceTest defines a single test to check the stateDiff tracer against.
type prestateTraceTest struct {
Genesis *core.Genesis `json:"genesis"`
Context *callContext `json:"context"`
Input string `json:"input"`
TracerConfig json.RawMessage `json:"tracerConfig"`
Result interface{} `json:"result"`
}
func TestPrestateTracer(t *testing.T) {
testPrestateDiffTracer("prestateTracer", "prestate_tracer", t, func() interface{} { return new(prestateTrace) })
}
func TestPrestateWithDiffModeTracer(t *testing.T) {
testPrestateDiffTracer("prestateTracer", "prestate_tracer_with_diff_mode", t, func() interface{} { return new(prePostStateTrace) })
}
func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T, typeBuilder func() interface{}) {
files, err := os.ReadDir(filepath.Join("testdata", dirPath))
if err != nil {
t.Fatalf("failed to retrieve tracer test suite: %v", err)
}
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".json") {
continue
}
file := file // capture range variable
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
t.Parallel()
var (
test = new(prestateTraceTest)
tx = new(types.Transaction)
)
// Call tracer test found, read if from disk
if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
t.Fatalf("failed to read testcase: %v", err)
} else if err := json.Unmarshal(blob, test); err != nil {
t.Fatalf("failed to parse testcase: %v", err)
}
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
t.Fatalf("failed to parse testcase input: %v", err)
}
// Configure a blockchain with the given prestate
var (
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
origin, _ = signer.Sender(tx)
txContext = vm.TxContext{
Origin: origin,
GasPrice: tx.GasPrice(),
}
context = vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
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),
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
msg, err := tx.AsMessage(signer, nil)
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 := typeBuilder()
if err := json.Unmarshal(res, ret); err != nil {
t.Fatalf("failed to unmarshal trace result: %v", err)
}
if !jsonEqual(ret, test.Result, typeBuilder(), typeBuilder()) {
// uncomment this for easier debugging
// have, _ := json.MarshalIndent(ret, "", " ")
// want, _ := json.MarshalIndent(test.Result, "", " ")
// t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
}
})
}
}
{
"genesis": {
"difficulty": "13756228101629",
"extraData": "0xd983010302844765746887676f312e342e328777696e646f7773",
"gasLimit": "3141592",
"hash": "0x58b7a87b6ba10b46b4e251d64ebc3d9822dd82218eaf24dff6796f6f1f687251",
"miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069",
"mixHash": "0x5984b9a316116bd890e6e5f4c52d655184b0d7aa74821e1382d7760f9803c1dd",
"nonce": "0xea4bb4997242c681",
"number": "1061221",
"stateRoot": "0x5402c04d481414248d824c3b61e924e0c9307adbc9fbaae774a74cce30a4163d",
"timestamp": "1456458069",
"totalDifficulty": "7930751135586064334",
"alloc": {
"0x2a65aca4d5fc5b5c859090a6c34d164135398226": {
"balance": "0x9fb6b81e112638b886",
"nonce": "217865",
"code": "0x"
},
"0xf0c5cef39b17c213cfe090a46b8c7760ffb7928a": {
"balance": "0x15b6828e22bb12188",
"nonce": "747",
"code": "0x"
}
},
"config": {
"chainId": 1,
"homesteadBlock": 1150000,
"daoForkBlock": 1920000,
"daoForkSupport": true,
"eip150Block": 2463000,
"eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
"eip155Block": 2675000,
"eip158Block": 2675000,
"byzantiumBlock": 4370000,
"constantinopleBlock": 7280000,
"petersburgBlock": 7280000,
"istanbulBlock": 9069000,
"muirGlacierBlock": 9200000,
"berlinBlock": 12244000,
"londonBlock": 12965000,
"arrowGlacierBlock": 13773000,
"grayGlacierBlock": 15050000,
"ethash": {}
}
},
"context": {
"number": "1061222",
"difficulty": "13749511193633",
"timestamp": "1456458097",
"gasLimit": "3141592",
"miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226"
},
"input": "0xf905498202eb850ba43b7400830f42408080b904f460606040526040516102b43803806102b48339016040526060805160600190602001505b5b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b806001600050908051906020019082805482825590600052602060002090601f01602090048101928215609e579182015b82811115609d5782518260005055916020019190600101906081565b5b50905060c5919060a9565b8082111560c1576000818150600090555060010160a9565b5090565b50505b506101dc806100d86000396000f30060606040526000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b514610044578063cfae32171461005157610042565b005b61004f6004506100ca565b005b61005c60045061015e565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015b57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b60206040519081016040528060008152602001506001600050805480601f016020809104026020016040519081016040528092919081815260200182805480156101cd57820191906000526020600020905b8154815290600101906020018083116101b057829003601f168201915b505050505090506101d9565b9056000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001ee7b225f6964223a225a473466784a7245323639384866623839222c22666f726d5f736f75726365223a22434c54523031222c22636f6d6d69746d656e745f64617465223a22222c22626f72726f7765725f6e616d65223a22222c22626f72726f7765725f616464726573735f6c696e6531223a22222c22626f72726f7765725f616464726573735f6c696e6532223a22222c22626f72726f7765725f636f6e74616374223a22222c22626f72726f7765725f7374617465223a22222c22626f72726f7765725f74797065223a22222c2270726f70657274795f61646472657373223a22222c226c6f616e5f616d6f756e745f7772697474656e223a22222c226c6f616e5f616d6f756e74223a22222c224c54565f7772697474656e223a22222c224c5456223a22222c2244534352223a22222c2270726f70657274795f74797065223a22222c2270726f70657274795f6465736372697074696f6e223a22222c226c656e646572223a22222c2267756172616e746f7273223a22222c226c696d69746564223a22222c226361705f616d6f756e74223a22222c226361705f70657263656e745f7772697474656e223a22222c226361705f70657263656e74616765223a22222c227465726d5f7772697474656e223a22222c227465726d223a22222c22657874656e64223a22227d0000000000000000000000000000000000001ba027d54712289af34f0ec0f06092745104d68e5801cd17097bc1104111f855258da070ec9f1c942d9bedf89f9660a684d3bb8cd9c2ac7f6dd883cb3e26a193180244",
"tracerConfig": {
"diffMode": true
},
"result": {
"pre": {
"0x2a65aca4d5fc5b5c859090a6c34d164135398226": {
"balance": "0x9fb6b81e112638b886",
"nonce": 217865
},
"0xf0c5cef39b17c213cfe090a46b8c7760ffb7928a": {
"balance": "0x15b6828e22bb12188",
"nonce": 747
}
},
"post": {
"0x2a65aca4d5fc5b5c859090a6c34d164135398226": {
"balance": "0x9fb71abdd2621d8886"
},
"0x40f2f445da6c9047554683fb382fba6769717116": {
"code": "0x60606040526000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b514610044578063cfae32171461005157610042565b005b61004f6004506100ca565b005b61005c60045061015e565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015b57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b60206040519081016040528060008152602001506001600050805480601f016020809104026020016040519081016040528092919081815260200182805480156101cd57820191906000526020600020905b8154815290600101906020018083116101b057829003601f168201915b505050505090506101d9565b9056",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f0c5cef39b17c213cfe090a46b8c7760ffb7928a",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000000000000000001ee",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x7b225f6964223a225a473466784a7245323639384866623839222c22666f726d",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7": "0x5f736f75726365223a22434c54523031222c22636f6d6d69746d656e745f6461",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf8": "0x7465223a22222c22626f72726f7765725f6e616d65223a22222c22626f72726f",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf9": "0x7765725f616464726573735f6c696e6531223a22222c22626f72726f7765725f",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfa": "0x616464726573735f6c696e6532223a22222c22626f72726f7765725f636f6e74",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfb": "0x616374223a22222c22626f72726f7765725f7374617465223a22222c22626f72",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfc": "0x726f7765725f74797065223a22222c2270726f70657274795f61646472657373",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfd": "0x223a22222c226c6f616e5f616d6f756e745f7772697474656e223a22222c226c",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfe": "0x6f616e5f616d6f756e74223a22222c224c54565f7772697474656e223a22222c",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cff": "0x224c5456223a22222c2244534352223a22222c2270726f70657274795f747970",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d00": "0x65223a22222c2270726f70657274795f6465736372697074696f6e223a22222c",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d01": "0x226c656e646572223a22222c2267756172616e746f7273223a22222c226c696d",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d02": "0x69746564223a22222c226361705f616d6f756e74223a22222c226361705f7065",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d03": "0x7263656e745f7772697474656e223a22222c226361705f70657263656e746167",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d04": "0x65223a22222c227465726d5f7772697474656e223a22222c227465726d223a22",
"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d05": "0x222c22657874656e64223a22227d000000000000000000000000000000000000"
}
},
"0xf0c5cef39b17c213cfe090a46b8c7760ffb7928a": {
"balance": "0x15b058920efcc5188",
"nonce": 748
}
}
}
}
{
"genesis": {
"difficulty": "6217248151198",
"extraData": "0xd783010103844765746887676f312e342e32856c696e7578",
"gasLimit": "3141592",
"hash": "0xe8bff55fe3e61936ef321cf3afaeb1ba2f7234e1e89535fa8ae39963caebe9c3",
"miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5",
"mixHash": "0x03da00d5a15a064e5ebddf53cd0aaeb9a8aff0f40c0fb031a74f463d11ec83b8",
"nonce": "0x6575fe08c4167044",
"number": "243825",
"stateRoot": "0x47182fe2e6e740b8a76f82fe5c527d6ad548f805274f21792cf4047235b24fbf",
"timestamp": "1442424328",
"totalDifficulty": "1035061827427752845",
"alloc": {
"0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": {
"balance": "0xc820f93200f4000",
"nonce": "0x5E",
"code": "0x"
},
"0x332b656504f4eabb44c8617a42af37461a34e9dc": {
"balance": "0x11faea4f35e5af80000",
"code": "0x",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": {
"balance": "0xbf681825be002ac452",
"nonce": "0x70FA",
"code": "0x"
},
"0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": {
"balance": "0xb3d0ac5cb94df6f6b0",
"nonce": "0x1",
"code": "0x"
}
},
"config": {
"chainId": 1,
"homesteadBlock": 1150000,
"daoForkBlock": 1920000,
"daoForkSupport": true,
"eip150Block": 2463000,
"eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0",
"eip155Block": 2675000,
"eip158Block": 2675000,
"byzantiumBlock": 4370000,
"constantinopleBlock": 7280000,
"petersburgBlock": 7280000,
"istanbulBlock": 9069000,
"muirGlacierBlock": 9200000,
"berlinBlock": 12244000,
"londonBlock": 12965000,
"arrowGlacierBlock": 13773000,
"grayGlacierBlock": 15050000,
"ethash": {}
}
},
"context": {
"number": "243826",
"difficulty": "6214212385501",
"timestamp": "1442424353",
"gasLimit": "3141592",
"miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5"
},
"input": "0xf8e85e850ba43b7400830f42408080b89660606040527382effbaaaf28614e55b2ba440fb198e0e5789b0f600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b600a80608c6000396000f30060606040526008565b001ca0340b21661e5bb85a46319a15f33a362e5c0f02faa7cdbf9c5808b2134da968eaa0226e6788f8c20e211d436ab7f6298ef32fa4c23a509eeeaac0880d115c17bc3f",
"tracerConfig": {
"diffMode": true
},
"result": {
"pre": {
"0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": {
"balance": "0xc820f93200f4000",
"nonce": 94
},
"0x332b656504f4eabb44c8617a42af37461a34e9dc": {
"balance": "0x11faea4f35e5af80000",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": {
"balance": "0xbf681825be002ac452",
"nonce": 28922
},
"0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": {
"balance": "0xb3d0ac5cb94df6f6b0",
"nonce": 1
}
},
"post": {
"0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": {
"balance": "0xc7d4d88af8b4c00",
"nonce": 95
},
"0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": {
"balance": "0xbf681ce7c870aeb852"
},
"0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": {
"balance": "0x1d37f515017a8eef6b0"
}
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
package tracetest
import (
"encoding/json"
"reflect"
"strings"
"unicode"
// Force-load native and js pacakges, to trigger registration
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
)
// 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) {
var nonce = genesis.alloc[key].nonce;
if (nonce) {
genesis.alloc[key].nonce = 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));
}
*/
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
// comparison
func jsonEqual(xi, yi, xt, yt interface{}) bool {
if xj, err := json.Marshal(xi); err == nil {
json.Unmarshal(xj, xt)
} else {
return false
}
if yj, err := json.Marshal(yi); err == nil {
json.Unmarshal(yj, yt)
} else {
return false
}
return reflect.DeepEqual(xt, yt)
}
// camel converts a snake cased input string into a camel cased output.
func camel(str string) string {
pieces := strings.Split(str, "_")
for i := 1; i < len(pieces); i++ {
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
}
return strings.Join(pieces, "")
}
...@@ -15,10 +15,10 @@ var _ = (*accountMarshaling)(nil) ...@@ -15,10 +15,10 @@ var _ = (*accountMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (a account) MarshalJSON() ([]byte, error) { func (a account) MarshalJSON() ([]byte, error) {
type account struct { type account struct {
Balance *hexutil.Big `json:"balance"` Balance *hexutil.Big `json:"balance,omitempty"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce,omitempty"`
Code hexutil.Bytes `json:"code"` Code hexutil.Bytes `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
} }
var enc account var enc account
enc.Balance = (*hexutil.Big)(a.Balance) enc.Balance = (*hexutil.Big)(a.Balance)
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package native package native
import ( import (
"bytes"
"encoding/json" "encoding/json"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
...@@ -35,12 +36,13 @@ func init() { ...@@ -35,12 +36,13 @@ func init() {
register("prestateTracer", newPrestateTracer) register("prestateTracer", newPrestateTracer)
} }
type prestate = map[common.Address]*account type state = map[common.Address]*account
type account struct { type account struct {
Balance *big.Int `json:"balance"` Balance *big.Int `json:"balance,omitempty"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce,omitempty"`
Code []byte `json:"code"` Code []byte `json:"code,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
} }
type accountMarshaling struct { type accountMarshaling struct {
...@@ -50,18 +52,38 @@ type accountMarshaling struct { ...@@ -50,18 +52,38 @@ type accountMarshaling struct {
type prestateTracer struct { type prestateTracer struct {
env *vm.EVM env *vm.EVM
prestate prestate pre state
post state
create bool create bool
to common.Address to common.Address
gasLimit uint64 // Amount of gas bought for the whole tx gasLimit uint64 // Amount of gas bought for the whole tx
config prestateTracerConfig
interrupt uint32 // Atomic flag to signal execution interruption interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
created map[common.Address]bool
deleted map[common.Address]bool
}
type prestateTracerConfig struct {
DiffMode bool `json:"diffMode"` // If true, this tracer will return all diff states
} }
func newPrestateTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
// First callframe contains tx context info // First callframe contains tx context info
// and is populated on start and end. // and is populated on start and end.
return &prestateTracer{prestate: prestate{}}, nil var config prestateTracerConfig
if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err
}
}
return &prestateTracer{
pre: state{},
post: state{},
config: config,
created: make(map[common.Address]bool),
deleted: make(map[common.Address]bool),
}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.
...@@ -72,26 +94,31 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo ...@@ -72,26 +94,31 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.lookupAccount(from) t.lookupAccount(from)
t.lookupAccount(to) t.lookupAccount(to)
t.lookupAccount(env.Context.Coinbase)
// The recipient balance includes the value transferred. // The recipient balance includes the value transferred.
toBal := new(big.Int).Sub(t.prestate[to].Balance, value) toBal := new(big.Int).Sub(t.pre[to].Balance, value)
t.prestate[to].Balance = toBal t.pre[to].Balance = toBal
// The sender balance is after reducing: value and gasLimit. // The sender balance is after reducing: value and gasLimit.
// We need to re-add them to get the pre-tx balance. // We need to re-add them to get the pre-tx balance.
fromBal := new(big.Int).Set(t.prestate[from].Balance) fromBal := new(big.Int).Set(t.pre[from].Balance)
gasPrice := env.TxContext.GasPrice gasPrice := env.TxContext.GasPrice
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.prestate[from].Balance = fromBal t.pre[from].Balance = fromBal
t.prestate[from].Nonce-- t.pre[from].Nonce--
if create && t.config.DiffMode {
t.created[to] = true
}
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
if t.create { if t.create && !t.config.DiffMode {
// Exclude created contract. // Exclude created contract.
delete(t.prestate, t.to) delete(t.pre, t.to)
} }
} }
...@@ -100,27 +127,34 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, ...@@ -100,27 +127,34 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
stack := scope.Stack stack := scope.Stack
stackData := stack.Data() stackData := stack.Data()
stackLen := len(stackData) stackLen := len(stackData)
caller := scope.Contract.Address()
switch { switch {
case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
slot := common.Hash(stackData[stackLen-1].Bytes32()) slot := common.Hash(stackData[stackLen-1].Bytes32())
t.lookupStorage(scope.Contract.Address(), slot) t.lookupStorage(caller, slot)
case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT):
addr := common.Address(stackData[stackLen-1].Bytes20()) addr := common.Address(stackData[stackLen-1].Bytes20())
t.lookupAccount(addr) t.lookupAccount(addr)
if op == vm.SELFDESTRUCT {
t.deleted[caller] = true
}
case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
addr := common.Address(stackData[stackLen-2].Bytes20()) addr := common.Address(stackData[stackLen-2].Bytes20())
t.lookupAccount(addr) t.lookupAccount(addr)
case op == vm.CREATE: case op == vm.CREATE:
addr := scope.Contract.Address() nonce := t.env.StateDB.GetNonce(caller)
nonce := t.env.StateDB.GetNonce(addr) addr := crypto.CreateAddress(caller, nonce)
t.lookupAccount(crypto.CreateAddress(addr, nonce)) t.lookupAccount(addr)
t.created[addr] = true
case stackLen >= 4 && op == vm.CREATE2: case stackLen >= 4 && op == vm.CREATE2:
offset := stackData[stackLen-2] offset := stackData[stackLen-2]
size := stackData[stackLen-3] size := stackData[stackLen-3]
init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
inithash := crypto.Keccak256(init) inithash := crypto.Keccak256(init)
salt := stackData[stackLen-4] salt := stackData[stackLen-4]
t.lookupAccount(crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash)) addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash)
t.lookupAccount(addr)
t.created[addr] = true
} }
} }
...@@ -141,12 +175,79 @@ func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { ...@@ -141,12 +175,79 @@ func (t *prestateTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit t.gasLimit = gasLimit
} }
func (t *prestateTracer) CaptureTxEnd(restGas uint64) {} func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
if !t.config.DiffMode {
return
}
for addr, state := range t.pre {
// the deleted account's state is pruned
if _, ok := t.deleted[addr]; ok {
continue
}
modified := false
postAccount := &account{Storage: make(map[common.Hash]common.Hash)}
newBalance := t.env.StateDB.GetBalance(addr)
newNonce := t.env.StateDB.GetNonce(addr)
newCode := t.env.StateDB.GetCode(addr)
if newBalance.Cmp(t.pre[addr].Balance) != 0 {
modified = true
postAccount.Balance = newBalance
}
if newNonce != t.pre[addr].Nonce {
modified = true
postAccount.Nonce = newNonce
}
if !bytes.Equal(newCode, t.pre[addr].Code) {
modified = true
postAccount.Code = newCode
}
for key, val := range state.Storage {
// don't include the empty slot
if val == (common.Hash{}) {
delete(t.pre[addr].Storage, key)
}
newVal := t.env.StateDB.GetState(addr, key)
if val != newVal {
modified = true
if newVal != (common.Hash{}) {
postAccount.Storage[key] = newVal
}
}
}
if modified {
t.post[addr] = postAccount
} else {
// if state is not modified, then no need to include into the pre state
delete(t.pre, addr)
}
}
// the new created contracts' prestate were empty, so delete them
for a := range t.created {
// the created contract maybe exists in statedb before the creating tx
if s := t.pre[a]; s.Balance.Cmp(big.NewInt(0)) == 0 && len(s.Storage) == 0 && len(s.Code) == 0 {
delete(t.pre, a)
}
}
}
// GetResult returns the json-encoded nested list of call traces, and any // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) { func (t *prestateTracer) GetResult() (json.RawMessage, error) {
res, err := json.Marshal(t.prestate) var res []byte
var err error
if t.config.DiffMode {
res, err = json.Marshal(struct {
Pre state `json:"pre"`
Post state `json:"post"`
}{t.pre, t.post})
} else {
res, err = json.Marshal(t.pre)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -162,10 +263,11 @@ func (t *prestateTracer) Stop(err error) { ...@@ -162,10 +263,11 @@ func (t *prestateTracer) Stop(err error) {
// lookupAccount fetches details of an account and adds it to the prestate // lookupAccount fetches details of an account and adds it to the prestate
// if it doesn't exist there. // if it doesn't exist there.
func (t *prestateTracer) lookupAccount(addr common.Address) { func (t *prestateTracer) lookupAccount(addr common.Address) {
if _, ok := t.prestate[addr]; ok { if _, ok := t.pre[addr]; ok {
return return
} }
t.prestate[addr] = &account{
t.pre[addr] = &account{
Balance: t.env.StateDB.GetBalance(addr), Balance: t.env.StateDB.GetBalance(addr),
Nonce: t.env.StateDB.GetNonce(addr), Nonce: t.env.StateDB.GetNonce(addr),
Code: t.env.StateDB.GetCode(addr), Code: t.env.StateDB.GetCode(addr),
...@@ -177,8 +279,8 @@ func (t *prestateTracer) lookupAccount(addr common.Address) { ...@@ -177,8 +279,8 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
// it to the prestate of the given contract. It assumes `lookupAccount` // it to the prestate of the given contract. It assumes `lookupAccount`
// has been performed on the contract before. // has been performed on the contract before.
func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) { func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) {
if _, ok := t.prestate[addr].Storage[key]; ok { if _, ok := t.pre[addr].Storage[key]; ok {
return return
} }
t.prestate[addr].Storage[key] = t.env.StateDB.GetState(addr, key) t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key)
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment