eth/tracers: use gencodec for native tracers (#25637)

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.
This commit is contained in:
Sina Mahmoodi 2022-09-26 18:35:44 +02:00 committed by GitHub
parent 3ec6fe6101
commit fc3e6d0162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 219 additions and 60 deletions

View File

@ -151,3 +151,7 @@ func (t *fourByteTracer) Stop(err error) {
t.reason = err t.reason = err
atomic.StoreUint32(&t.interrupt, 1) atomic.StoreUint32(&t.interrupt, 1)
} }
func bytesToHex(s []byte) string {
return "0x" + common.Bytes2Hex(s)
}

View File

@ -20,31 +20,47 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"math/big" "math/big"
"strconv"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/common" "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/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "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() { func init() {
register("callTracer", newCallTracer) register("callTracer", newCallTracer)
} }
type callFrame struct { type callFrame struct {
Type string `json:"type"` Type vm.OpCode `json:"-"`
From string `json:"from"` From common.Address `json:"from"`
To string `json:"to,omitempty"` Gas uint64 `json:"gas"`
Value string `json:"value,omitempty"` GasUsed uint64 `json:"gasUsed"`
Gas string `json:"gas"` To common.Address `json:"to,omitempty" rlp:"optional"`
GasUsed string `json:"gasUsed"` Input []byte `json:"input" rlp:"optional"`
Input string `json:"input"` Output []byte `json:"output,omitempty" rlp:"optional"`
Output string `json:"output,omitempty"` Error string `json:"error,omitempty" rlp:"optional"`
Error string `json:"error,omitempty"` Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
Calls []callFrame `json:"calls,omitempty"` // 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 { type callTracer struct {
@ -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) { 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.env = env
t.callstack[0] = callFrame{ t.callstack[0] = callFrame{
Type: "CALL", Type: vm.CALL,
From: addrToHex(from), From: from,
To: addrToHex(to), To: to,
Input: bytesToHex(input), Input: common.CopyBytes(input),
Gas: uintToHex(gas), Gas: gas,
Value: bigToHex(value), Value: value,
} }
if create { if create {
t.callstack[0].Type = "CREATE" t.callstack[0].Type = vm.CREATE
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { 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 { if err != nil {
t.callstack[0].Error = err.Error() t.callstack[0].Error = err.Error()
if err.Error() == "execution reverted" && len(output) > 0 { if err.Error() == "execution reverted" && len(output) > 0 {
t.callstack[0].Output = bytesToHex(output) t.callstack[0].Output = output
} }
} else { } else {
t.callstack[0].Output = bytesToHex(output) t.callstack[0].Output = output
} }
} }
@ -122,12 +139,12 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
} }
call := callFrame{ call := callFrame{
Type: typ.String(), Type: typ,
From: addrToHex(from), From: from,
To: addrToHex(to), To: to,
Input: bytesToHex(input), Input: common.CopyBytes(input),
Gas: uintToHex(gas), Gas: gas,
Value: bigToHex(value), Value: value,
} }
t.callstack = append(t.callstack, call) t.callstack = append(t.callstack, call)
} }
@ -147,13 +164,13 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack = t.callstack[:size-1] t.callstack = t.callstack[:size-1]
size -= 1 size -= 1
call.GasUsed = uintToHex(gasUsed) call.GasUsed = gasUsed
if err == nil { if err == nil {
call.Output = bytesToHex(output) call.Output = common.CopyBytes(output)
} else { } else {
call.Error = err.Error() call.Error = err.Error()
if call.Type == "CREATE" || call.Type == "CREATE2" { if call.Type == vm.CREATE || call.Type == vm.CREATE2 {
call.To = "" call.To = common.Address{}
} }
} }
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
@ -181,22 +198,3 @@ func (t *callTracer) Stop(err error) {
t.reason = err t.reason = err
atomic.StoreUint32(&t.interrupt, 1) 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())
}

View File

@ -0,0 +1,56 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package native
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*accountMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (a account) MarshalJSON() ([]byte, error) {
type account struct {
Balance *hexutil.Big `json:"balance"`
Nonce uint64 `json:"nonce"`
Code hexutil.Bytes `json:"code"`
Storage map[common.Hash]common.Hash `json:"storage"`
}
var enc account
enc.Balance = (*hexutil.Big)(a.Balance)
enc.Nonce = a.Nonce
enc.Code = a.Code
enc.Storage = a.Storage
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (a *account) UnmarshalJSON(input []byte) error {
type account struct {
Balance *hexutil.Big `json:"balance"`
Nonce *uint64 `json:"nonce"`
Code *hexutil.Bytes `json:"code"`
Storage map[common.Hash]common.Hash `json:"storage"`
}
var dec account
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Balance != nil {
a.Balance = (*big.Int)(dec.Balance)
}
if dec.Nonce != nil {
a.Nonce = *dec.Nonce
}
if dec.Code != nil {
a.Code = *dec.Code
}
if dec.Storage != nil {
a.Storage = dec.Storage
}
return nil
}

View File

@ -0,0 +1,95 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package native
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
)
var _ = (*callFrameMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (c callFrame) MarshalJSON() ([]byte, error) {
type callFrame0 struct {
Type vm.OpCode `json:"-"`
From common.Address `json:"from"`
To common.Address `json:"to,omitempty" rlp:"optional"`
Gas hexutil.Uint64 `json:"gas"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
Input hexutil.Bytes `json:"input" rlp:"optional"`
Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
Error string `json:"error,omitempty" rlp:"optional"`
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
TypeString string `json:"type"`
}
var enc callFrame0
enc.Type = c.Type
enc.From = c.From
enc.To = c.To
enc.Gas = hexutil.Uint64(c.Gas)
enc.GasUsed = hexutil.Uint64(c.GasUsed)
enc.Input = c.Input
enc.Output = c.Output
enc.Error = c.Error
enc.Calls = c.Calls
enc.Value = (*hexutil.Big)(c.Value)
enc.TypeString = c.TypeString()
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (c *callFrame) UnmarshalJSON(input []byte) error {
type callFrame0 struct {
Type *vm.OpCode `json:"-"`
From *common.Address `json:"from"`
To *common.Address `json:"to,omitempty" rlp:"optional"`
Gas *hexutil.Uint64 `json:"gas"`
GasUsed *hexutil.Uint64 `json:"gasUsed"`
Input *hexutil.Bytes `json:"input" rlp:"optional"`
Output *hexutil.Bytes `json:"output,omitempty" rlp:"optional"`
Error *string `json:"error,omitempty" rlp:"optional"`
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
Value *hexutil.Big `json:"value,omitempty" rlp:"optional"`
}
var dec callFrame0
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Type != nil {
c.Type = *dec.Type
}
if dec.From != nil {
c.From = *dec.From
}
if dec.To != nil {
c.To = *dec.To
}
if dec.Gas != nil {
c.Gas = uint64(*dec.Gas)
}
if dec.GasUsed != nil {
c.GasUsed = uint64(*dec.GasUsed)
}
if dec.Input != nil {
c.Input = *dec.Input
}
if dec.Output != nil {
c.Output = *dec.Output
}
if dec.Error != nil {
c.Error = *dec.Error
}
if dec.Calls != nil {
c.Calls = dec.Calls
}
if dec.Value != nil {
c.Value = (*big.Int)(dec.Value)
}
return nil
}

View File

@ -29,18 +29,25 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers" "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() { func init() {
register("prestateTracer", newPrestateTracer) register("prestateTracer", newPrestateTracer)
} }
type prestate = map[common.Address]*account type prestate = map[common.Address]*account
type account struct { type account struct {
Balance string `json:"balance"` Balance *big.Int `json:"balance"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce"`
Code string `json:"code"` Code []byte `json:"code"`
Storage map[common.Hash]common.Hash `json:"storage"` Storage map[common.Hash]common.Hash `json:"storage"`
} }
type accountMarshaling struct {
Balance *hexutil.Big
Code hexutil.Bytes
}
type prestateTracer struct { type prestateTracer struct {
env *vm.EVM env *vm.EVM
prestate prestate prestate prestate
@ -67,17 +74,16 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
t.lookupAccount(to) t.lookupAccount(to)
// The recipient balance includes the value transferred. // The recipient balance includes the value transferred.
toBal := hexutil.MustDecodeBig(t.prestate[to].Balance) toBal := new(big.Int).Sub(t.prestate[to].Balance, value)
toBal = new(big.Int).Sub(toBal, value) t.prestate[to].Balance = toBal
t.prestate[to].Balance = hexutil.EncodeBig(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 := hexutil.MustDecodeBig(t.prestate[from].Balance) fromBal := t.prestate[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 = hexutil.EncodeBig(fromBal) t.prestate[from].Balance = fromBal
t.prestate[from].Nonce-- t.prestate[from].Nonce--
} }
@ -160,9 +166,9 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
return return
} }
t.prestate[addr] = &account{ t.prestate[addr] = &account{
Balance: bigToHex(t.env.StateDB.GetBalance(addr)), Balance: t.env.StateDB.GetBalance(addr),
Nonce: t.env.StateDB.GetNonce(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), Storage: make(map[common.Hash]common.Hash),
} }
} }