eth/tracers: improve tracing performance (#23016)

Improves the performance of debug.traceTransaction
This commit is contained in:
Martin Holst Swende 2021-07-01 09:15:04 +02:00 committed by GitHub
parent bbbeb7d8ba
commit f5f906dd0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 34 deletions

@ -4,11 +4,11 @@ package vm
import ( import (
"encoding/json" "encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/holiman/uint256"
) )
var _ = (*structLogMarshaling)(nil) var _ = (*structLogMarshaling)(nil)
@ -22,8 +22,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
GasCost math.HexOrDecimal64 `json:"gasCost"` GasCost math.HexOrDecimal64 `json:"gasCost"`
Memory hexutil.Bytes `json:"memory"` Memory hexutil.Bytes `json:"memory"`
MemorySize int `json:"memSize"` MemorySize int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []uint256.Int `json:"stack"`
ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
ReturnData hexutil.Bytes `json:"returnData"` ReturnData hexutil.Bytes `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
@ -39,12 +38,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
enc.GasCost = math.HexOrDecimal64(s.GasCost) enc.GasCost = math.HexOrDecimal64(s.GasCost)
enc.Memory = s.Memory enc.Memory = s.Memory
enc.MemorySize = s.MemorySize enc.MemorySize = s.MemorySize
if s.Stack != nil { enc.Stack = s.Stack
enc.Stack = make([]*math.HexOrDecimal256, len(s.Stack))
for k, v := range s.Stack {
enc.Stack[k] = (*math.HexOrDecimal256)(v)
}
}
enc.ReturnData = s.ReturnData enc.ReturnData = s.ReturnData
enc.Storage = s.Storage enc.Storage = s.Storage
enc.Depth = s.Depth enc.Depth = s.Depth
@ -64,7 +58,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
GasCost *math.HexOrDecimal64 `json:"gasCost"` GasCost *math.HexOrDecimal64 `json:"gasCost"`
Memory *hexutil.Bytes `json:"memory"` Memory *hexutil.Bytes `json:"memory"`
MemorySize *int `json:"memSize"` MemorySize *int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []uint256.Int `json:"stack"`
ReturnData *hexutil.Bytes `json:"returnData"` ReturnData *hexutil.Bytes `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"` Depth *int `json:"depth"`
@ -94,10 +88,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
s.MemorySize = *dec.MemorySize s.MemorySize = *dec.MemorySize
} }
if dec.Stack != nil { if dec.Stack != nil {
s.Stack = make([]*big.Int, len(dec.Stack)) s.Stack = dec.Stack
for k, v := range dec.Stack {
s.Stack[k] = (*big.Int)(v)
}
} }
if dec.ReturnData != nil { if dec.ReturnData != nil {
s.ReturnData = *dec.ReturnData s.ReturnData = *dec.ReturnData

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
) )
// Storage represents a contract's storage. // Storage represents a contract's storage.
@ -66,7 +67,7 @@ type StructLog struct {
GasCost uint64 `json:"gasCost"` GasCost uint64 `json:"gasCost"`
Memory []byte `json:"memory"` Memory []byte `json:"memory"`
MemorySize int `json:"memSize"` MemorySize int `json:"memSize"`
Stack []*big.Int `json:"stack"` Stack []uint256.Int `json:"stack"`
ReturnData []byte `json:"returnData"` ReturnData []byte `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
@ -76,7 +77,6 @@ type StructLog struct {
// overrides for gencodec // overrides for gencodec
type structLogMarshaling struct { type structLogMarshaling struct {
Stack []*math.HexOrDecimal256
Gas math.HexOrDecimal64 Gas math.HexOrDecimal64
GasCost math.HexOrDecimal64 GasCost math.HexOrDecimal64
Memory hexutil.Bytes Memory hexutil.Bytes
@ -135,6 +135,14 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
return logger return logger
} }
// Reset clears the data held by the logger.
func (l *StructLogger) Reset() {
l.storage = make(map[common.Address]Storage)
l.output = make([]byte, 0)
l.logs = l.logs[:0]
l.err = nil
}
// CaptureStart implements the Tracer interface to initialize the tracing operation. // CaptureStart implements the Tracer interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
} }
@ -157,16 +165,16 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
copy(mem, memory.Data()) copy(mem, memory.Data())
} }
// Copy a snapshot of the current stack state to a new buffer // Copy a snapshot of the current stack state to a new buffer
var stck []*big.Int var stck []uint256.Int
if !l.cfg.DisableStack { if !l.cfg.DisableStack {
stck = make([]*big.Int, len(stack.Data())) stck = make([]uint256.Int, len(stack.Data()))
for i, item := range stack.Data() { for i, item := range stack.Data() {
stck[i] = new(big.Int).Set(item.ToBig()) stck[i] = item
} }
} }
// Copy a snapshot of the current storage to a new container // Copy a snapshot of the current storage to a new container
var storage Storage var storage Storage
if !l.cfg.DisableStorage { if !l.cfg.DisableStorage && (op == SLOAD || op == SSTORE) {
// initialise new changed values storage container for this contract // initialise new changed values storage container for this contract
// if not present. // if not present.
if l.storage[contract.Address()] == nil { if l.storage[contract.Address()] == nil {
@ -179,16 +187,16 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
value = env.StateDB.GetState(contract.Address(), address) value = env.StateDB.GetState(contract.Address(), address)
) )
l.storage[contract.Address()][address] = value l.storage[contract.Address()][address] = value
} storage = l.storage[contract.Address()].Copy()
// capture SSTORE opcodes and record the written entry in the local storage. } else if op == SSTORE && stack.len() >= 2 {
if op == SSTORE && stack.len() >= 2 { // capture SSTORE opcodes and record the written entry in the local storage.
var ( var (
value = common.Hash(stack.data[stack.len()-2].Bytes32()) value = common.Hash(stack.data[stack.len()-2].Bytes32())
address = common.Hash(stack.data[stack.len()-1].Bytes32()) address = common.Hash(stack.data[stack.len()-1].Bytes32())
) )
l.storage[contract.Address()][address] = value l.storage[contract.Address()][address] = value
storage = l.storage[contract.Address()].Copy()
} }
storage = l.storage[contract.Address()].Copy()
} }
var rdata []byte var rdata []byte
if !l.cfg.DisableReturnData { if !l.cfg.DisableReturnData {
@ -238,7 +246,7 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
if len(log.Stack) > 0 { if len(log.Stack) > 0 {
fmt.Fprintln(writer, "Stack:") fmt.Fprintln(writer, "Stack:")
for i := len(log.Stack) - 1; i >= 0; i-- { for i := len(log.Stack) - 1; i >= 0; i-- {
fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
} }
} }
if len(log.Memory) > 0 { if len(log.Memory) > 0 {
@ -314,7 +322,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
// format stack // format stack
var a []string var a []string
for _, elem := range stack.data { for _, elem := range stack.data {
a = append(a, fmt.Sprintf("%v", elem.String())) a = append(a, elem.Hex())
} }
b := fmt.Sprintf("[%v]", strings.Join(a, ",")) b := fmt.Sprintf("[%v]", strings.Join(a, ","))
fmt.Fprintf(t.out, "%10v |", b) fmt.Fprintf(t.out, "%10v |", b)

@ -57,7 +57,6 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
Gas: gas, Gas: gas,
GasCost: cost, GasCost: cost,
MemorySize: memory.Len(), MemorySize: memory.Len(),
Storage: nil,
Depth: depth, Depth: depth,
RefundCounter: env.StateDB.GetRefund(), RefundCounter: env.StateDB.GetRefund(),
Err: err, Err: err,
@ -66,12 +65,7 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
log.Memory = memory.Data() log.Memory = memory.Data()
} }
if !l.cfg.DisableStack { if !l.cfg.DisableStack {
//TODO(@holiman) improve this log.Stack = stack.data
logstack := make([]*big.Int, len(stack.Data()))
for i, item := range stack.Data() {
logstack[i] = item.ToBig()
}
log.Stack = logstack
} }
if !l.cfg.DisableReturnData { if !l.cfg.DisableReturnData {
log.ReturnData = rData log.ReturnData = rData

@ -300,3 +300,81 @@ func jsonEqual(x, y interface{}) bool {
} }
return reflect.DeepEqual(xTrace, yTrace) return reflect.DeepEqual(xTrace, yTrace)
} }
func BenchmarkTransactionTrace(b *testing.B) {
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
from := crypto.PubkeyToAddress(key.PublicKey)
gas := uint64(1000000) // 1M gas
to := common.HexToAddress("0x00000000000000000000000000000000deadbeef")
signer := types.LatestSignerForChainID(big.NewInt(1337))
tx, err := types.SignNewTx(key, signer,
&types.LegacyTx{
Nonce: 1,
GasPrice: big.NewInt(500),
Gas: gas,
To: &to,
})
if err != nil {
b.Fatal(err)
}
txContext := vm.TxContext{
Origin: from,
GasPrice: tx.GasPrice(),
}
context := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Coinbase: common.Address{},
BlockNumber: new(big.Int).SetUint64(uint64(5)),
Time: new(big.Int).SetUint64(uint64(5)),
Difficulty: big.NewInt(0xffffffff),
GasLimit: gas,
}
alloc := core.GenesisAlloc{}
// The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns
// the address
loop := []byte{
byte(vm.JUMPDEST), // [ count ]
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = core.GenesisAccount{
Nonce: 1,
Code: loop,
Balance: big.NewInt(1),
}
alloc[from] = core.GenesisAccount{
Nonce: 1,
Code: []byte{},
Balance: big.NewInt(500000000000000),
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it
tracer := vm.NewStructLogger(&vm.LogConfig{
Debug: false,
//DisableStorage: true,
//DisableMemory: true,
//DisableReturnData: true,
})
evm := vm.NewEVM(context, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer})
msg, err := tx.AsMessage(signer, nil)
if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
snap := statedb.Snapshot()
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
_, err = st.TransitionDb()
if err != nil {
b.Fatal(err)
}
statedb.RevertToSnapshot(snap)
if have, want := len(tracer.StructLogs()), 244752; have != want {
b.Fatalf("trace wrong, want %d steps, have %d", want, have)
}
tracer.Reset()
}
}

@ -1117,7 +1117,7 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes {
if trace.Stack != nil { if trace.Stack != nil {
stack := make([]string, len(trace.Stack)) stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack { for i, stackValue := range trace.Stack {
stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32)) stack[i] = stackValue.Hex()
} }
formatted[index].Stack = &stack formatted[index].Stack = &stack
} }