Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(trace): clean outdated fields #842

Merged
merged 17 commits into from
Jul 15, 2024
Merged
45 changes: 13 additions & 32 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"`
lispc marked this conversation as resolved.
Show resolved Hide resolved
Code string `json:"code"`
}

// StorageTrace stores proofs of storage needed by storage circuit
type StorageTrace struct {
// Root hash before block execution:
Expand Down Expand Up @@ -62,12 +71,10 @@ type ExecutionResult struct {
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"`
PoseidonCodeHash *common.Hash `json:"poseidonCodeHash,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 +90,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 +110,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
86 changes: 31 additions & 55 deletions core/vm/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/codehash"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params"
)
Expand Down Expand Up @@ -76,7 +77,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 +96,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 +137,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 +153,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 +168,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 +181,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 +203,18 @@ func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Add
Nonce: env.StateDB.GetNonce(to),
Balance: (*hexutil.Big)(value),
}

code := input

codeInfo := CodeInfo{
CodeSize: uint64(len(code)),
KeccakCodeHash: codehash.KeccakCodeHash(code),
PoseidonCodeHash: codehash.PoseidonCodeHash(code),
Code: code,
}
l.bytecodes[codeInfo.PoseidonCodeHash] = codeInfo
} else {
traceCodeWithAddress(l, to)
}

l.statesAffected[from] = struct{}{}
Expand Down Expand Up @@ -260,17 +275,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 +297,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 +337,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 +347,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 +361,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 +517,6 @@ func FormatLogs(logs []*StructLog) []*types.StructLogRes {
}
logRes.Storage = storage
}
logRes.ExtraData = trace.ExtraData

formatted = append(formatted, logRes)
}
Expand Down
62 changes: 18 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,68 @@ 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)},
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)
}
Loading
Loading