Skip to content

Commit

Permalink
eth/tracers: use gencodec for native tracers (#25637)
Browse files Browse the repository at this point in the history
The call tracer and prestate tracer store data JSON-encoded in memory. In order to support alternative encodings (specifically RLP), it's better to keep data a native format during tracing. This PR does marshalling at the end, using gencodec.

OBS! 
This PR changes the call tracer result slightly:

-  Order of type and value fields are changed (should not matter). 
-  Output fields are completely omitted when they're empty (no more output: "0x"). Previously, this was only _sometimes_ omitted (e.g. when call ended in a non-revert error) and otherwise 0x when the output was actually empty.
  • Loading branch information
s1na authored Sep 26, 2022
1 parent 3ec6fe6 commit fc3e6d0
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 60 deletions.
4 changes: 4 additions & 0 deletions eth/tracers/native/4byte.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,7 @@ func (t *fourByteTracer) Stop(err error) {
t.reason = err
atomic.StoreUint32(&t.interrupt, 1)
}

func bytesToHex(s []byte) string {
return "0x" + common.Bytes2Hex(s)
}
100 changes: 49 additions & 51 deletions eth/tracers/native/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,47 @@ import (
"encoding/json"
"errors"
"math/big"
"strconv"
"strings"
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)

//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go

func init() {
register("callTracer", newCallTracer)
}

type callFrame struct {
Type string `json:"type"`
From string `json:"from"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Calls []callFrame `json:"calls,omitempty"`
Type vm.OpCode `json:"-"`
From common.Address `json:"from"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
To common.Address `json:"to,omitempty" rlp:"optional"`
Input []byte `json:"input" rlp:"optional"`
Output []byte `json:"output,omitempty" rlp:"optional"`
Error string `json:"error,omitempty" rlp:"optional"`
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
// Placed at end on purpose. The RLP will be decoded to 0 instead of
// nil if there are non-empty elements after in the struct.
Value *big.Int `json:"value,omitempty" rlp:"optional"`
}

func (f callFrame) TypeString() string {
return f.Type.String()
}

type callFrameMarshaling struct {
TypeString string `json:"type"`
Gas hexutil.Uint64
GasUsed hexutil.Uint64
Value *hexutil.Big
Input hexutil.Bytes
Output hexutil.Bytes
}

type callTracer struct {
Expand Down Expand Up @@ -77,28 +93,29 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
t.callstack[0] = callFrame{
Type: "CALL",
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
Type: vm.CALL,
From: from,
To: to,
Input: common.CopyBytes(input),
Gas: gas,
Value: value,
}
if create {
t.callstack[0].Type = "CREATE"
t.callstack[0].Type = vm.CREATE
}
}

// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
t.callstack[0].GasUsed = uintToHex(gasUsed)
t.callstack[0].GasUsed = gasUsed
output = common.CopyBytes(output)
if err != nil {
t.callstack[0].Error = err.Error()
if err.Error() == "execution reverted" && len(output) > 0 {
t.callstack[0].Output = bytesToHex(output)
t.callstack[0].Output = output
}
} else {
t.callstack[0].Output = bytesToHex(output)
t.callstack[0].Output = output
}
}

Expand All @@ -122,12 +139,12 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
}

call := callFrame{
Type: typ.String(),
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
Type: typ,
From: from,
To: to,
Input: common.CopyBytes(input),
Gas: gas,
Value: value,
}
t.callstack = append(t.callstack, call)
}
Expand All @@ -147,13 +164,13 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack = t.callstack[:size-1]
size -= 1

call.GasUsed = uintToHex(gasUsed)
call.GasUsed = gasUsed
if err == nil {
call.Output = bytesToHex(output)
call.Output = common.CopyBytes(output)
} else {
call.Error = err.Error()
if call.Type == "CREATE" || call.Type == "CREATE2" {
call.To = ""
if call.Type == vm.CREATE || call.Type == vm.CREATE2 {
call.To = common.Address{}
}
}
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
Expand Down Expand Up @@ -181,22 +198,3 @@ func (t *callTracer) Stop(err error) {
t.reason = err
atomic.StoreUint32(&t.interrupt, 1)
}

func bytesToHex(s []byte) string {
return "0x" + common.Bytes2Hex(s)
}

func bigToHex(n *big.Int) string {
if n == nil {
return ""
}
return "0x" + n.Text(16)
}

func uintToHex(n uint64) string {
return "0x" + strconv.FormatUint(n, 16)
}

func addrToHex(a common.Address) string {
return strings.ToLower(a.Hex())
}
56 changes: 56 additions & 0 deletions eth/tracers/native/gen_account_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions eth/tracers/native/gen_callframe_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 15 additions & 9 deletions eth/tracers/native/prestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,25 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers"
)

//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go

func init() {
register("prestateTracer", newPrestateTracer)
}

type prestate = map[common.Address]*account
type account struct {
Balance string `json:"balance"`
Balance *big.Int `json:"balance"`
Nonce uint64 `json:"nonce"`
Code string `json:"code"`
Code []byte `json:"code"`
Storage map[common.Hash]common.Hash `json:"storage"`
}

type accountMarshaling struct {
Balance *hexutil.Big
Code hexutil.Bytes
}

type prestateTracer struct {
env *vm.EVM
prestate prestate
Expand All @@ -67,17 +74,16 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.lookupAccount(to)

// The recipient balance includes the value transferred.
toBal := hexutil.MustDecodeBig(t.prestate[to].Balance)
toBal = new(big.Int).Sub(toBal, value)
t.prestate[to].Balance = hexutil.EncodeBig(toBal)
toBal := new(big.Int).Sub(t.prestate[to].Balance, value)
t.prestate[to].Balance = toBal

// The sender balance is after reducing: value and gasLimit.
// We need to re-add them to get the pre-tx balance.
fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance)
fromBal := t.prestate[from].Balance
gasPrice := env.TxContext.GasPrice
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.prestate[from].Balance = hexutil.EncodeBig(fromBal)
t.prestate[from].Balance = fromBal
t.prestate[from].Nonce--
}

Expand Down Expand Up @@ -160,9 +166,9 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
return
}
t.prestate[addr] = &account{
Balance: bigToHex(t.env.StateDB.GetBalance(addr)),
Balance: t.env.StateDB.GetBalance(addr),
Nonce: t.env.StateDB.GetNonce(addr),
Code: bytesToHex(t.env.StateDB.GetCode(addr)),
Code: t.env.StateDB.GetCode(addr),
Storage: make(map[common.Hash]common.Hash),
}
}
Expand Down

0 comments on commit fc3e6d0

Please sign in to comment.