Skip to content

Commit

Permalink
refactor(trace): clean outdated fields (#842)
Browse files Browse the repository at this point in the history
* clean trace: remove ExtraData, disable prestateTracer, collect bytecodes in top level

* fix

* fix

* fix

* fix

* fix

* more cleanup

* more cleanup

* refactor(trace): clean outdated fields

* fix bytecodes in trace

* chore: auto version bump [bot]

* chore: auto version bump [bot]

* use hexutil.Bytes

---------

Co-authored-by: Ömer Faruk Irmak <omerfirmak@gmail.com>
Co-authored-by: omerfirmak <omerfirmak@users.noreply.github.com>
Co-authored-by: lispc <lispc@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 15, 2024
1 parent df08f60 commit 0cecc19
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 159 deletions.
40 changes: 9 additions & 31 deletions core/types/l2trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@ type BlockTrace struct {
Header *Header `json:"header"`
Transactions []*TransactionData `json:"transactions"`
StorageTrace *StorageTrace `json:"storageTrace"`
Bytecodes []*BytecodeTrace `json:"codes"`
TxStorageTraces []*StorageTrace `json:"txStorageTraces,omitempty"`
ExecutionResults []*ExecutionResult `json:"executionResults"`
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
StartL1QueueIndex uint64 `json:"startL1QueueIndex"`
}

// BytecodeTrace stores all accessed bytecodes
type BytecodeTrace struct {
CodeSize uint64 `json:"codeSize"`
KeccakCodeHash common.Hash `json:"keccakCodeHash"`
PoseidonCodeHash common.Hash `json:"hash"`
Code hexutil.Bytes `json:"code"`
}

// StorageTrace stores proofs of storage needed by storage circuit
type StorageTrace struct {
// Root hash before block execution:
Expand Down Expand Up @@ -61,13 +70,8 @@ type ExecutionResult struct {
// currently they are just `from` and `to` account
AccountsAfter []*AccountWrapper `json:"accountAfter"`

// `PoseidonCodeHash` only exists when tx is a contract call.
PoseidonCodeHash *common.Hash `json:"poseidonCodeHash,omitempty"`
// If it is a contract call, the contract code is returned.
ByteCode string `json:"byteCode,omitempty"`
StructLogs []*StructLogRes `json:"structLogs"`
CallTrace json.RawMessage `json:"callTrace"`
Prestate json.RawMessage `json:"prestate"`
}

// StructLogRes stores a structured log emitted by the EVM while replaying a
Expand All @@ -83,7 +87,6 @@ type StructLogRes struct {
Memory []string `json:"memory,omitempty"`
Storage map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
ExtraData *ExtraData `json:"extraData,omitempty"`
}

// NewStructLogResBasic Basic StructLogRes skeleton, Stack&Memory&Storage&ExtraData are separated from it for GC optimization;
Expand All @@ -104,31 +107,6 @@ func NewStructLogResBasic(pc uint64, op string, gas, gasCost uint64, depth int,
return logRes
}

type ExtraData struct {
// Indicate the call succeeds or not for CALL/CREATE op
CallFailed bool `json:"callFailed,omitempty"`
// CALL | CALLCODE | DELEGATECALL | STATICCALL: [tx.to address’s code, stack.nth_last(1) address’s code]
// CREATE | CREATE2: [created contract’s code]
// CODESIZE | CODECOPY: [contract’s code]
// EXTCODESIZE | EXTCODECOPY: [stack.nth_last(0) address’s code]
CodeList []string `json:"codeList,omitempty"`
// SSTORE | SLOAD: [storageProof]
// SELFDESTRUCT: [contract address’s account, stack.nth_last(0) address’s account]
// SELFBALANCE: [contract address’s account]
// BALANCE | EXTCODEHASH: [stack.nth_last(0) address’s account]
// CREATE | CREATE2: [created contract address’s account (before constructed),
// created contract address's account (after constructed)]
// CALL | CALLCODE: [caller contract address’s account,
// stack.nth_last(1) (i.e. callee) address’s account,
// callee contract address's account (value updated, before called)]
// STATICCALL: [stack.nth_last(1) (i.e. callee) address’s account,
// callee contract address's account (before called)]
StateList []*AccountWrapper `json:"proofList,omitempty"`
// The status of caller, it would be captured twice:
// 1. before execution and 2. updated in CaptureEnter (for CALL/CALLCODE it duplicated with StateList[0])
Caller []*AccountWrapper `json:"caller,omitempty"`
}

type AccountWrapper struct {
Address common.Address `json:"address"`
Nonce uint64 `json:"nonce"`
Expand Down
75 changes: 20 additions & 55 deletions core/vm/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ type StructLog struct {
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"`
ExtraData *types.ExtraData `json:"extraData"`
Err error `json:"-"`
}

Expand All @@ -96,17 +95,9 @@ func (s *StructLog) clean() {
s.Stack = s.Stack[:0]
s.ReturnData.Reset()
s.Storage = nil
s.ExtraData = nil
s.Err = nil
}

func (s *StructLog) getOrInitExtraData() *types.ExtraData {
if s.ExtraData == nil {
s.ExtraData = &types.ExtraData{}
}
return s.ExtraData
}

// overrides for gencodec
type structLogMarshaling struct {
Gas math.HexOrDecimal64
Expand Down Expand Up @@ -145,6 +136,13 @@ type EVMLogger interface {
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
}

type CodeInfo struct {
CodeSize uint64
KeccakCodeHash common.Hash
PoseidonCodeHash common.Hash
Code []byte
}

// StructLogger is an EVM state logger and implements EVMLogger.
//
// StructLogger can capture state based on the given Log configuration and also keeps
Expand All @@ -154,6 +152,8 @@ type StructLogger struct {
cfg LogConfig
env *EVM

bytecodes map[common.Hash]CodeInfo

statesAffected map[common.Address]struct{}
storage map[common.Address]Storage
createdAccount *types.AccountWrapper
Expand All @@ -167,6 +167,7 @@ type StructLogger struct {
// NewStructLogger returns a new logger
func NewStructLogger(cfg *LogConfig) *StructLogger {
logger := &StructLogger{
bytecodes: make(map[common.Hash]CodeInfo),
storage: make(map[common.Address]Storage),
statesAffected: make(map[common.Address]struct{}),
}
Expand All @@ -179,6 +180,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {

// Reset clears the data held by the logger.
func (l *StructLogger) Reset() {
l.bytecodes = make(map[common.Hash]CodeInfo)
l.storage = make(map[common.Address]Storage)
l.statesAffected = make(map[common.Address]struct{})
l.output = make([]byte, 0)
Expand All @@ -200,6 +202,8 @@ func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Add
Nonce: env.StateDB.GetNonce(to),
Balance: (*hexutil.Big)(value),
}
} else {
traceCodeWithAddress(l, to)
}

l.statesAffected[from] = struct{}{}
Expand Down Expand Up @@ -260,17 +264,11 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop
if ok {
// execute trace func list.
for _, exec := range execFuncList {
if err := exec(l, scope, structLog.getOrInitExtraData()); err != nil {
if err := exec(l, scope); err != nil {
log.Error("Failed to trace data", "opcode", op.String(), "err", err)
}
}
}
// for each "calling" op, pick the caller's state
switch op {
case CALL, CALLCODE, STATICCALL, DELEGATECALL, CREATE, CREATE2:
extraData := structLog.getOrInitExtraData()
extraData.Caller = append(extraData.Caller, getWrappedAccountForAddr(l, scope.Contract.Address()))
}

// in reality it is impossible for CREATE to trigger ErrContractAddressCollision
if op == CREATE2 && opErr == nil {
Expand All @@ -288,9 +286,6 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop

contractHash := l.env.StateDB.GetKeccakCodeHash(address)
if l.env.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyKeccakCodeHash) {
extraData := structLog.getOrInitExtraData()
wrappedStatus := getWrappedAccountForAddr(l, address)
extraData.StateList = append(extraData.StateList, wrappedStatus)
l.statesAffected[address] = struct{}{}
}
}
Expand Down Expand Up @@ -331,16 +326,7 @@ func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.A
panic("unexpected evm depth in capture enter")
}
l.statesAffected[to] = struct{}{}
theLog := l.logs[lastLogPos]
theLog.getOrInitExtraData()
// handling additional updating for CALL/STATICCALL/CALLCODE/CREATE/CREATE2 only
// append extraData part for the log, capture the account status (the nonce / balance has been updated in capture enter)
wrappedStatus := getWrappedAccountForAddr(l, to)
theLog.ExtraData.StateList = append(theLog.ExtraData.StateList, wrappedStatus)
// finally we update the caller's status (it is possible that nonce and balance being updated)
if len(theLog.ExtraData.Caller) == 1 {
theLog.ExtraData.Caller = append(theLog.ExtraData.Caller, getWrappedAccountForAddr(l, from))
}

}

// CaptureExit phase, a CREATE has its target address's code being set and queryable
Expand All @@ -350,32 +336,7 @@ func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
panic("unexpected capture exit occur")
}

theLogPos := l.callStackLogInd[stackH-1]
l.callStackLogInd = l.callStackLogInd[:stackH-1]
theLog := l.logs[theLogPos]
// update "forecast" data
if err != nil {
theLog.ExtraData.CallFailed = true
}

// handling updating for CREATE only
switch theLog.Op {
case CREATE, CREATE2:
// append extraData part for the log whose op is CREATE(2), capture the account status (the codehash would be updated in capture exit)
dataLen := len(theLog.ExtraData.StateList)
if dataLen == 0 {
panic("unexpected data capture for target op")
}

lastAccData := theLog.ExtraData.StateList[dataLen-1]
wrappedStatus := getWrappedAccountForAddr(l, lastAccData.Address)
theLog.ExtraData.StateList = append(theLog.ExtraData.StateList, wrappedStatus)
code := getCodeForAddr(l, lastAccData.Address)
theLog.ExtraData.CodeList = append(theLog.ExtraData.CodeList, hexutil.Encode(code))
default:
//do nothing for other op code
return
}

}

Expand All @@ -389,6 +350,11 @@ func (l *StructLogger) UpdatedStorages() map[common.Address]Storage {
return l.storage
}

// TracedBytecodes is used to collect all "touched" bytecodes
func (l *StructLogger) TracedBytecodes() map[common.Hash]CodeInfo {
return l.bytecodes
}

// CreatedAccount return the account data in case it is a create tx
func (l *StructLogger) CreatedAccount() *types.AccountWrapper { return l.createdAccount }

Expand Down Expand Up @@ -540,7 +506,6 @@ func FormatLogs(logs []*StructLog) []*types.StructLogRes {
}
logRes.Storage = storage
}
logRes.ExtraData = trace.ExtraData

formatted = append(formatted, logRes)
}
Expand Down
63 changes: 19 additions & 44 deletions core/vm/logger_trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package vm

import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core/types"
)

type traceFunc func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error
type traceFunc func(l *StructLogger, scope *ScopeContext) error

var (
// OpcodeExecs the map to load opcodes' trace funcs.
Expand All @@ -15,92 +13,69 @@ var (
CALLCODE: {traceToAddressCode, traceLastNAddressCode(1), traceContractAccount, traceLastNAddressAccount(1)}, // contract account is the caller, stack.nth_last(1) is the callee's address
DELEGATECALL: {traceToAddressCode, traceLastNAddressCode(1)},
STATICCALL: {traceToAddressCode, traceLastNAddressCode(1), traceLastNAddressAccount(1)},
CREATE: {}, // caller is already recorded in ExtraData.Caller, callee is recorded in CaptureEnter&CaptureExit
CREATE2: {}, // caller is already recorded in ExtraData.Caller, callee is recorded in CaptureEnter&CaptureExit
SLOAD: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag
SSTORE: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag
SELFDESTRUCT: {traceContractAccount, traceLastNAddressAccount(0)},
SELFBALANCE: {traceContractAccount},
BALANCE: {traceLastNAddressAccount(0)},
EXTCODEHASH: {traceLastNAddressAccount(0)},
CODESIZE: {traceContractCode},
CODECOPY: {traceContractCode},
EXTCODESIZE: {traceLastNAddressCode(0)},
EXTCODESIZE: {traceLastNAddressAccount(0)},
EXTCODECOPY: {traceLastNAddressCode(0)},
}
)

// traceToAddressCode gets tx.to address’s code
func traceToAddressCode(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
func traceToAddressCode(l *StructLogger, scope *ScopeContext) error {
if l.env.To == nil {
return nil
}
code := l.env.StateDB.GetCode(*l.env.To)
extraData.CodeList = append(extraData.CodeList, hexutil.Encode(code))
traceCodeWithAddress(l, *l.env.To)
return nil
}

// traceLastNAddressCode
func traceLastNAddressCode(n int) traceFunc {
return func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
return func(l *StructLogger, scope *ScopeContext) error {
stack := scope.Stack
if stack.len() <= n {
return nil
}
address := common.Address(stack.data[stack.len()-1-n].Bytes20())
code := l.env.StateDB.GetCode(address)
extraData.CodeList = append(extraData.CodeList, hexutil.Encode(code))
traceCodeWithAddress(l, address)
l.statesAffected[address] = struct{}{}
return nil
}
}

// traceContractCode gets the contract's code
func traceContractCode(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
code := l.env.StateDB.GetCode(scope.Contract.Address())
extraData.CodeList = append(extraData.CodeList, hexutil.Encode(code))
return nil
func traceCodeWithAddress(l *StructLogger, address common.Address) {
code := l.env.StateDB.GetCode(address)
keccakCodeHash := l.env.StateDB.GetKeccakCodeHash(address)
poseidonCodeHash := l.env.StateDB.GetPoseidonCodeHash(address)
codeSize := l.env.StateDB.GetCodeSize(address)
l.bytecodes[poseidonCodeHash] = CodeInfo{
codeSize,
keccakCodeHash,
poseidonCodeHash,
code,
}
}

// traceContractAccount gets the contract's account
func traceContractAccount(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
// Get account state.
state := getWrappedAccountForAddr(l, scope.Contract.Address())
extraData.StateList = append(extraData.StateList, state)
func traceContractAccount(l *StructLogger, scope *ScopeContext) error {
l.statesAffected[scope.Contract.Address()] = struct{}{}

return nil
}

// traceLastNAddressAccount returns func about the last N's address account.
func traceLastNAddressAccount(n int) traceFunc {
return func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
return func(l *StructLogger, scope *ScopeContext) error {
stack := scope.Stack
if stack.len() <= n {
return nil
}

address := common.Address(stack.data[stack.len()-1-n].Bytes20())
state := getWrappedAccountForAddr(l, address)
extraData.StateList = append(extraData.StateList, state)
l.statesAffected[address] = struct{}{}

return nil
}
}

// StorageWrapper will be empty
func getWrappedAccountForAddr(l *StructLogger, address common.Address) *types.AccountWrapper {
return &types.AccountWrapper{
Address: address,
Nonce: l.env.StateDB.GetNonce(address),
Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)),
KeccakCodeHash: l.env.StateDB.GetKeccakCodeHash(address),
PoseidonCodeHash: l.env.StateDB.GetPoseidonCodeHash(address),
CodeSize: l.env.StateDB.GetCodeSize(address),
}
}

func getCodeForAddr(l *StructLogger, address common.Address) []byte {
return l.env.StateDB.GetCode(address)
}
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 5 // Minor version component of the current release
VersionPatch = 11 // Patch version component of the current release
VersionPatch = 12 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

Expand Down
Loading

0 comments on commit 0cecc19

Please sign in to comment.