From fc3e6d0162a0689228bf396e9903da79bb630cb2 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Mon, 26 Sep 2022 18:35:44 +0200 Subject: [PATCH] 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. --- eth/tracers/native/4byte.go | 4 + eth/tracers/native/call.go | 100 +++++++++++------------ eth/tracers/native/gen_account_json.go | 56 +++++++++++++ eth/tracers/native/gen_callframe_json.go | 95 +++++++++++++++++++++ eth/tracers/native/prestate.go | 24 ++++-- 5 files changed, 219 insertions(+), 60 deletions(-) create mode 100644 eth/tracers/native/gen_account_json.go create mode 100644 eth/tracers/native/gen_callframe_json.go diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 7fb1c5e6c..29f3bccb1 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -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) +} diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 7af0e658a..54ae6dc8a 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -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 { @@ -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 } } @@ -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) } @@ -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) @@ -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()) -} diff --git a/eth/tracers/native/gen_account_json.go b/eth/tracers/native/gen_account_json.go new file mode 100644 index 000000000..25dc77dc7 --- /dev/null +++ b/eth/tracers/native/gen_account_json.go @@ -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 +} diff --git a/eth/tracers/native/gen_callframe_json.go b/eth/tracers/native/gen_callframe_json.go new file mode 100644 index 000000000..baf0e32e6 --- /dev/null +++ b/eth/tracers/native/gen_callframe_json.go @@ -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 +} diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index b513f383b..918143a33 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -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 @@ -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-- } @@ -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), } }