Skip to content

Commit f294c6a

Browse files
committed
add statediff tracer
1 parent 2281373 commit f294c6a

File tree

7 files changed

+307
-18
lines changed

7 files changed

+307
-18
lines changed

eth/tracers/api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bufio"
2121
"bytes"
2222
"context"
23+
"encoding/json"
2324
"errors"
2425
"fmt"
2526
"io/ioutil"
@@ -172,6 +173,8 @@ type TraceConfig struct {
172173
Tracer *string
173174
Timeout *string
174175
Reexec *uint64
176+
177+
TracerConfig json.RawMessage
175178
}
176179

177180
// TraceCallConfig is the config for traceCall API. It holds one more
@@ -935,7 +938,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
935938
return nil, err
936939
}
937940
}
938-
if t, err := New(*config.Tracer, txctx); err != nil {
941+
if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil {
939942
return nil, err
940943
} else {
941944
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)

eth/tracers/js/tracer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ type jsTracer struct {
424424
// New instantiates a new tracer instance. code specifies a Javascript snippet,
425425
// which must evaluate to an expression returning an object with 'step', 'fault'
426426
// and 'result' functions.
427-
func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) {
427+
func newJsTracer(code string, ctx *tracers2.Context, cfg json.RawMessage) (tracers2.Tracer, error) {
428428
if c, ok := assetTracers[code]; ok {
429429
code = c
430430
}

eth/tracers/native/call.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ type callTracer struct {
5656

5757
// newCallTracer returns a native go tracer which tracks
5858
// call frames of a tx, and implements vm.EVMLogger.
59-
func newCallTracer() tracers.Tracer {
59+
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
6060
// First callframe contains tx context info
6161
// and is populated on start and end.
6262
t := &callTracer{callstack: make([]callFrame, 1)}
63-
return t
63+
return t, nil
6464
}
6565

6666
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.

eth/tracers/native/noop.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ func init() {
3535
type noopTracer struct{}
3636

3737
// newNoopTracer returns a new noop tracer.
38-
func newNoopTracer() tracers.Tracer {
39-
return &noopTracer{}
38+
func newNoopTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
39+
return &noopTracer{}, nil
4040
}
4141

4242
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.

eth/tracers/native/prestate.go

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// Copyright 2022 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 native
18+
19+
import (
20+
"encoding/json"
21+
"github.com/ethereum/go-ethereum/common/hexutil"
22+
"math/big"
23+
"sync/atomic"
24+
"time"
25+
26+
"github.com/ethereum/go-ethereum/common"
27+
"github.com/ethereum/go-ethereum/core/vm"
28+
"github.com/ethereum/go-ethereum/crypto"
29+
"github.com/ethereum/go-ethereum/eth/tracers"
30+
)
31+
32+
func init() {
33+
register("stateDiff", newStateDiffTracer)
34+
}
35+
36+
type state = map[common.Address]*account
37+
type account struct {
38+
Balance string `json:"balance,omitempty"`
39+
Nonce uint64 `json:"nonce,omitempty"`
40+
storage map[common.Hash]common.Hash
41+
}
42+
43+
func (a account) exists() bool {
44+
bi := new(big.Int)
45+
bi.SetString(a.Balance[2:], 16)
46+
return a.Nonce > 0 || (bi.Sign() != 0)
47+
}
48+
49+
type stateDiffTracer struct {
50+
env *vm.EVM
51+
pre state
52+
post state
53+
to common.Address
54+
create bool
55+
config stateDiffTracerConfig
56+
gasLimit uint64 // Amount of gas bought for the whole tx
57+
interrupt uint32 // Atomic flag to signal execution interruption
58+
reason error // Textual reason for the interruption
59+
created map[common.Address]bool
60+
deleted map[common.Address]bool
61+
}
62+
63+
type stateDiffTracerConfig struct {
64+
DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
65+
}
66+
67+
func newStateDiffTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
68+
var config stateDiffTracerConfig
69+
if cfg != nil {
70+
if err := json.Unmarshal(cfg, &config); err != nil {
71+
return nil, err
72+
}
73+
}
74+
// and is populated on start and end.
75+
return &stateDiffTracer{
76+
pre: state{},
77+
post: state{},
78+
config: config,
79+
created: make(map[common.Address]bool),
80+
deleted: make(map[common.Address]bool),
81+
}, nil
82+
}
83+
84+
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
85+
func (t *stateDiffTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
86+
t.env = env
87+
t.create = create
88+
t.to = to
89+
90+
t.lookupAccount(from)
91+
t.lookupAccount(to)
92+
t.lookupAccount(env.Context.Coinbase)
93+
//t.lookupAccount(consensus.SystemAddress)
94+
95+
// The recipient balance includes the value transferred.
96+
toAccount := t.pre[to]
97+
toBal := hexutil.MustDecodeBig(toAccount.Balance)
98+
toBal = new(big.Int).Sub(toBal, value)
99+
toAccount.Balance = hexutil.EncodeBig(toBal)
100+
t.pre[to] = toAccount
101+
102+
// The sender balance is after reducing: value and gasLimit.
103+
// We need to re-add them to get the pre-tx balance.
104+
fromAccount := t.pre[from]
105+
fromBal := hexutil.MustDecodeBig(fromAccount.Balance)
106+
gasPrice := env.TxContext.GasPrice
107+
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
108+
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
109+
fromAccount.Balance = hexutil.EncodeBig(fromBal)
110+
fromAccount.Nonce--
111+
t.pre[from] = fromAccount
112+
113+
if create && t.config.DiffMode {
114+
t.created[to] = true
115+
}
116+
}
117+
118+
// CaptureEnd is called after the call finishes to finalize the tracing.
119+
func (t *stateDiffTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
120+
if err != nil {
121+
return
122+
}
123+
if t.config.DiffMode {
124+
return
125+
}
126+
127+
if t.create {
128+
// Keep existing account prior to contract creation at that address
129+
if s := t.pre[t.to]; !s.exists() {
130+
// Exclude newly created contract.
131+
delete(t.pre, t.to)
132+
}
133+
}
134+
}
135+
136+
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
137+
func (t *stateDiffTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
138+
if err != nil {
139+
return
140+
}
141+
stack := scope.Stack
142+
stackData := stack.Data()
143+
stackLen := len(stackData)
144+
caller := scope.Contract.Address()
145+
switch {
146+
case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
147+
slot := common.Hash(stackData[stackLen-1].Bytes32())
148+
t.lookupStorage(caller, slot)
149+
case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT):
150+
addr := common.Address(stackData[stackLen-1].Bytes20())
151+
t.lookupAccount(addr)
152+
if op == vm.SELFDESTRUCT {
153+
t.deleted[caller] = true
154+
}
155+
case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
156+
addr := common.Address(stackData[stackLen-2].Bytes20())
157+
t.lookupAccount(addr)
158+
case op == vm.CREATE:
159+
nonce := t.env.StateDB.GetNonce(caller)
160+
addr := crypto.CreateAddress(caller, nonce)
161+
t.lookupAccount(addr)
162+
t.created[addr] = true
163+
case stackLen >= 4 && op == vm.CREATE2:
164+
offset := stackData[stackLen-2]
165+
size := stackData[stackLen-3]
166+
init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
167+
inithash := crypto.Keccak256(init)
168+
salt := stackData[stackLen-4]
169+
addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash)
170+
t.lookupAccount(addr)
171+
t.created[addr] = true
172+
}
173+
}
174+
175+
// CaptureFault implements the EVMLogger interface to trace an execution fault.
176+
func (t *stateDiffTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
177+
}
178+
179+
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
180+
func (t *stateDiffTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
181+
}
182+
183+
// CaptureExit is called when EVM exits a scope, even if the scope didn't
184+
// execute any code.
185+
func (t *stateDiffTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
186+
}
187+
188+
func (t *stateDiffTracer) CaptureTxStart(gasLimit uint64) {
189+
t.gasLimit = gasLimit
190+
}
191+
192+
func (t *stateDiffTracer) CaptureTxEnd(restGas uint64) {
193+
if !t.config.DiffMode {
194+
return
195+
}
196+
197+
//for addr, state := range t.pre {
198+
for addr, _ := range t.pre {
199+
// The deleted account's state is pruned from `post` but kept in `pre`
200+
if _, ok := t.deleted[addr]; ok {
201+
continue
202+
}
203+
modified := false
204+
postAccount := account{storage: make(map[common.Hash]common.Hash)}
205+
newBalance := bigToHex(t.env.StateDB.GetBalance(addr))
206+
newNonce := t.env.StateDB.GetNonce(addr)
207+
208+
if newBalance != t.pre[addr].Balance {
209+
modified = true
210+
postAccount.Balance = newBalance
211+
}
212+
if newNonce != t.pre[addr].Nonce {
213+
modified = true
214+
postAccount.Nonce = newNonce
215+
}
216+
217+
if modified {
218+
t.post[addr] = &postAccount
219+
} else {
220+
// if state is not modified, then no need to include into the pre state
221+
delete(t.pre, addr)
222+
}
223+
}
224+
// the new created contracts' prestate were empty, so delete them
225+
for a := range t.created {
226+
// the created contract maybe exists in statedb before the creating tx
227+
if s := t.pre[a]; !s.exists() {
228+
delete(t.pre, a)
229+
}
230+
}
231+
}
232+
233+
// GetResult returns the json-encoded nested list of call traces, and any
234+
// error arising from the encoding or forceful termination (via `Stop`).
235+
func (t *stateDiffTracer) GetResult() (json.RawMessage, error) {
236+
var res []byte
237+
var err error
238+
if t.config.DiffMode {
239+
res, err = json.Marshal(struct {
240+
Post state `json:"post"`
241+
Pre state `json:"pre"`
242+
}{t.post, t.pre})
243+
} else {
244+
res, err = json.Marshal(t.pre)
245+
}
246+
if err != nil {
247+
return nil, err
248+
}
249+
return json.RawMessage(res), t.reason
250+
}
251+
252+
// Stop terminates execution of the tracer at the first opportune moment.
253+
func (t *stateDiffTracer) Stop(err error) {
254+
t.reason = err
255+
atomic.StoreUint32(&t.interrupt, 1)
256+
}
257+
258+
// lookupAccount fetches details of an account and adds it to the prestate
259+
// if it doesn't exist there.
260+
func (t *stateDiffTracer) lookupAccount(addr common.Address) {
261+
if _, ok := t.pre[addr]; ok {
262+
return
263+
}
264+
265+
t.pre[addr] = &account{
266+
Balance: bigToHex(t.env.StateDB.GetBalance(addr)),
267+
Nonce: t.env.StateDB.GetNonce(addr),
268+
storage: make(map[common.Hash]common.Hash),
269+
}
270+
}
271+
272+
// lookupStorage fetches the requested storage slot and adds
273+
// it to the prestate of the given contract. It assumes `lookupAccount`
274+
// has been performed on the contract before.
275+
func (t *stateDiffTracer) lookupStorage(addr common.Address, key common.Hash) {
276+
if _, ok := t.pre[addr].storage[key]; ok {
277+
return
278+
}
279+
t.pre[addr].storage[key] = t.env.StateDB.GetState(addr, key)
280+
}

0 commit comments

Comments
 (0)