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
|
t.reason = err
|
||||||
atomic.StoreUint32(&t.interrupt, 1)
|
atomic.StoreUint32(&t.interrupt, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bytesToHex(s []byte) string {
|
||||||
|
return "0x" + common.Bytes2Hex(s)
|
||||||
|
}
|
||||||
|
@ -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())
|
|
||||||
}
|
|
||||||
|
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"
|
"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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user