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:
parent
3ec6fe6101
commit
fc3e6d0162
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
56
eth/tracers/native/gen_account_json.go
Normal file
56
eth/tracers/native/gen_account_json.go
Normal 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
|
||||
}
|
95
eth/tracers/native/gen_callframe_json.go
Normal file
95
eth/tracers/native/gen_callframe_json.go
Normal 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
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user