feat: add scroll trace (#620)

* add rollup/tracing/tracing.go

* update eth/tracers/logger/access_list_tracer.go

* update core/types/l2trace.go

* update core/vm/evm.go

* update core/evm.go

* update core/vm/interpreter.go

* update eth/tracers/logger/logger_json.go

* update core/vm/logger_trace.go

* mv core/vm/logger_trace.go

mv core/vm/logger_trace.go

* update eth/tracers/api_blocktrace.go

* update eth/tracers/js/tracer.go

* update `EVMLogger` interface

* fix `JSONLogger`'s `CaptureStateAfter`

* fix `OpcodeExecs`

* minor fixes

* fix eth/tracers/api_blocktrace.go

* comment out eth/tracers/logger/logger_trace.go

* fix

* update eth/tracers/api.go

* some renamings

* update cmd/utils/flags.go

* update rollup/tracing/tracing.go WIP

* fix interface

* minor

* fix `FormatLogs`

* Fix tracers (#663)

* export `CallTracer`

* export `CallTracer`

* export `PrestateTracer`

* export `MuxTracer`

* refactor

* merge `StructLogRes` (#662)

* merge `StructLogRes`

* clean up

* fix

* fix

* update core/block_validator.go

* update ethclient/ethclient.go

* merge `ExecutionResult` (#667)

* merge `ExecutionResult`

* fix l1datafee

* fix eth/tracers/api_test.go (#669)

* clean up

* WIP: update eth/tracers/logger/logger.go

* update `CaptureStart`

* update `CaptureExit`

* init `CaptureEnter`

* update `CaptureEnter` 1

* fix `CaptureEnter` 1

* fix depth

* fix `CaptureEnter` 2

* update `CaptureEnter` 2
This commit is contained in:
HAOYUatHZ 2024-03-16 10:26:10 +08:00 committed by GitHub
parent 2829e66aea
commit 4ebc036d75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1399 additions and 134 deletions

View file

@ -71,6 +71,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/tracing"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/triedb/hashdb" "github.com/ethereum/go-ethereum/trie/triedb/hashdb"
@ -2003,7 +2004,8 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
if err != nil { if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err) Fatalf("Failed to register the Ethereum service: %v", err)
} }
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) scrollTracerWrapper := tracing.NewTracerWrapper()
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper))
return backend.ApiBackend, nil return backend.ApiBackend, nil
} }
@ -2032,7 +2034,8 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
Fatalf("Failed to create the LES server: %v", err) Fatalf("Failed to create the LES server: %v", err)
} }
} }
stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) scrollTracerWrapper := tracing.NewTracerWrapper()
stack.RegisterAPIs(tracers.APIs(backend.APIBackend, scrollTracerWrapper))
return backend.APIBackend, backend return backend.APIBackend, backend
} }

View file

@ -19,13 +19,16 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/circuitcapacitychecker"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -37,6 +40,12 @@ type BlockValidator struct {
config *params.ChainConfig // Chain configuration options config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for validating engine consensus.Engine // Consensus engine used for validating
// circuit capacity checker related fields
checkCircuitCapacity bool // whether enable circuit capacity check
cMu sync.Mutex // mutex for circuit capacity checker
tracer tracerWrapper // scroll tracer wrapper
circuitCapacityChecker *circuitcapacitychecker.CircuitCapacityChecker // circuit capacity checker instance
} }
// NewBlockValidator returns a new block validator which is safe for re-use // NewBlockValidator returns a new block validator which is safe for re-use
@ -49,6 +58,17 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
return validator return validator
} }
type tracerWrapper interface {
CreateTraceEnvAndGetBlockTrace(*params.ChainConfig, ChainContext, consensus.Engine, ethdb.Database, *state.StateDB, *types.Block, *types.Block, bool) (*types.BlockTrace, error)
}
func (v *BlockValidator) SetupTracerAndCircuitCapacityChecker(tracer tracerWrapper) {
v.checkCircuitCapacity = true
v.tracer = tracer
v.circuitCapacityChecker = circuitcapacitychecker.NewCircuitCapacityChecker(true)
log.Info("new CircuitCapacityChecker in BlockValidator", "ID", v.circuitCapacityChecker.ID)
}
// ValidateBody validates the given block's uncles and verifies the block // ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already // header's transaction and uncle roots. The headers are assumed to be already
// validated at this point. // validated at this point.
@ -131,6 +151,27 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
return err return err
} }
if v.checkCircuitCapacity {
// if a block's RowConsumption has been stored, which means it has been processed before,
// (e.g., in miner/worker.go or in insertChain),
// we simply skip its calculation and validation
// if rawdb.ReadBlockRowConsumption(v.bc.db, block.Hash()) != nil {
// return nil
// }
rowConsumption, err := v.validateCircuitRowConsumption(block)
if err != nil {
return err
}
log.Trace(
"Validator write block row consumption",
"id", v.circuitCapacityChecker.ID,
"number", block.NumberU64(),
"hash", block.Hash().String(),
"rowConsumption", rowConsumption,
)
// rawdb.WriteBlockRowConsumption(v.bc.db, block.Hash(), rowConsumption)
}
return nil return nil
} }
@ -271,3 +312,51 @@ func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 {
} }
return limit return limit
} }
func (v *BlockValidator) createTraceEnvAndGetBlockTrace(block *types.Block) (*types.BlockTrace, error) {
parent := v.bc.GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return nil, errors.New("validateCircuitRowConsumption: no parent block found")
}
statedb, err := v.bc.StateAt(parent.Root())
if err != nil {
return nil, err
}
return v.tracer.CreateTraceEnvAndGetBlockTrace(v.config, v.bc, v.engine, v.bc.db, statedb, parent, block, true)
}
func (v *BlockValidator) validateCircuitRowConsumption(block *types.Block) (*types.RowConsumption, error) {
log.Trace(
"Validator apply ccc for block",
"id", v.circuitCapacityChecker.ID,
"number", block.NumberU64(),
"hash", block.Hash().String(),
"len(txs)", block.Transactions().Len(),
)
traces, err := v.createTraceEnvAndGetBlockTrace(block)
if err != nil {
return nil, err
}
v.cMu.Lock()
defer v.cMu.Unlock()
v.circuitCapacityChecker.Reset()
log.Trace("Validator reset ccc", "id", v.circuitCapacityChecker.ID)
rc, err := v.circuitCapacityChecker.ApplyBlock(traces)
log.Trace(
"Validator apply ccc for block result",
"id", v.circuitCapacityChecker.ID,
"number", block.NumberU64(),
"hash", block.Hash().String(),
"len(txs)", block.Transactions().Len(),
"rc", rc,
"err", err,
)
return rc, err
}

View file

@ -81,6 +81,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, chainConfig *p
func NewEVMTxContext(msg *Message) vm.TxContext { func NewEVMTxContext(msg *Message) vm.TxContext {
return vm.TxContext{ return vm.TxContext{
Origin: msg.From, Origin: msg.From,
To: msg.To,
GasPrice: new(big.Int).Set(msg.GasPrice), GasPrice: new(big.Int).Set(msg.GasPrice),
BlobHashes: msg.BlobHashes, BlobHashes: msg.BlobHashes,
} }

View file

@ -13,7 +13,7 @@ import (
) )
type TrieProve interface { type TrieProve interface {
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error Prove(key []byte, proofDb ethdb.KeyValueWriter) error
} }
type ZktrieProofTracer struct { type ZktrieProofTracer struct {
@ -77,9 +77,9 @@ func (s *StateDB) GetSecureTrieProof(trieProve TrieProve, key common.Hash) ([][]
var err error var err error
if s.IsUsingZktrie() { if s.IsUsingZktrie() {
key_s, _ := zkt.ToSecureKeyBytes(key.Bytes()) key_s, _ := zkt.ToSecureKeyBytes(key.Bytes())
err = trieProve.Prove(key_s.Bytes(), 0, &proof) err = trieProve.Prove(key_s.Bytes(), &proof)
} else { } else {
err = trieProve.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) err = trieProve.Prove(crypto.Keccak256(key.Bytes()), &proof)
} }
return proof, err return proof, err
} }

View file

@ -23,6 +23,8 @@ import (
"sort" "sort"
"time" "time"
zkt "github.com/scroll-tech/zktrie/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
@ -34,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/trie/zkproof"
) )
const ( const (
@ -358,6 +361,26 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
return common.Hash{} return common.Hash{}
} }
// GetProof returns the Merkle proof for a given account.
func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) {
if s.IsUsingZktrie() {
addr_s, _ := zkt.ToSecureKeyBytes(addr.Bytes())
return s.GetProofByHash(common.BytesToHash(addr_s.Bytes()))
}
return s.GetProofByHash(crypto.Keccak256Hash(addr.Bytes()))
}
// GetProofByHash returns the Merkle proof for a given account.
func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
var proof zkproof.ProofList
err := s.trie.Prove(addrHash[:] /*, 0*/, &proof)
return proof, err
}
func (s *StateDB) GetRootHash() common.Hash {
return s.trie.Hash()
}
// GetCommittedState retrieves a value from the given account's committed storage trie. // GetCommittedState retrieves a value from the given account's committed storage trie.
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)

View file

@ -46,10 +46,11 @@ type StorageTrace struct {
// while replaying a transaction in debug mode as well as transaction // while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value // execution status, the amount of gas used and the return value
type ExecutionResult struct { type ExecutionResult struct {
L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"` Gas uint64 `json:"gas"`
Gas uint64 `json:"gas"` Failed bool `json:"failed"`
Failed bool `json:"failed"` ReturnValue string `json:"returnValue"`
ReturnValue string `json:"returnValue"` StructLogs []StructLogRes `json:"structLogs"`
// Sender's account state (before Tx) // Sender's account state (before Tx)
From *AccountWrapper `json:"from,omitempty"` From *AccountWrapper `json:"from,omitempty"`
// Receiver's account state (before Tx) // Receiver's account state (before Tx)
@ -65,24 +66,29 @@ type ExecutionResult struct {
// `PoseidonCodeHash` only exists when tx is a contract call. // `PoseidonCodeHash` only exists when tx is a contract call.
PoseidonCodeHash *common.Hash `json:"poseidonCodeHash,omitempty"` PoseidonCodeHash *common.Hash `json:"poseidonCodeHash,omitempty"`
// If it is a contract call, the contract code is returned. // If it is a contract call, the contract code is returned.
ByteCode string `json:"byteCode,omitempty"` ByteCode string `json:"byteCode,omitempty"`
StructLogs []*StructLogRes `json:"structLogs"`
L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"`
CallTrace json.RawMessage `json:"callTrace"`
PrestateTrace json.RawMessage `json:"prestateTrace"`
} }
// StructLogRes stores a structured log emitted by the EVM while replaying a // StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode // transaction in debug mode
type StructLogRes struct { type StructLogRes struct {
Pc uint64 `json:"pc"` Pc uint64 `json:"pc"`
Op string `json:"op"` Op string `json:"op"`
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"` GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"` Depth int `json:"depth"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
Stack []string `json:"stack,omitempty"` Stack *[]string `json:"stack,omitempty"`
Memory []string `json:"memory,omitempty"` ReturnData string `json:"returnData,omitempty"`
Storage map[string]string `json:"storage,omitempty"` Memory *[]string `json:"memory,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"` Storage *map[string]string `json:"storage,omitempty"`
ExtraData *ExtraData `json:"extraData,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; // NewStructLogResBasic Basic StructLogRes skeleton, Stack&Memory&Storage&ExtraData are separated from it for GC optimization;

View file

@ -49,6 +49,11 @@ func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) {
return val, val < offset64 return val, val < offset64
} }
// GetData exports getData
func GetData(data []byte, start uint64, size uint64) []byte {
return getData(data, start, size)
}
// getData returns a slice from the data based on the start and size and pads // getData returns a slice from the data based on the start and size and pads
// up to size with zero's. This function is overflow safe. // up to size with zero's. This function is overflow safe.
func getData(data []byte, start uint64, size uint64) []byte { func getData(data []byte, start uint64, size uint64) []byte {

View file

@ -83,9 +83,10 @@ type BlockContext struct {
// All fields can change between transactions. // All fields can change between transactions.
type TxContext struct { type TxContext struct {
// Message information // Message information
Origin common.Address // Provides information for ORIGIN Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE To *common.Address // Provides information for TO in trace
BlobHashes []common.Hash // Provides information for BLOBHASH GasPrice *big.Int // Provides information for GASPRICE
BlobHashes []common.Hash // Provides information for BLOBHASH
} }
// EVM is the Ethereum Virtual Machine base object and provides // EVM is the Ethereum Virtual Machine base object and provides
@ -531,3 +532,8 @@ func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
func (evm *EVM) FeeRecipient() common.Address { func (evm *EVM) FeeRecipient() common.Address {
return evm.Context.Coinbase return evm.Context.Coinbase
} }
// Depth returns the environment's depth of the current call stack.
func (evm *EVM) Depth() int {
return evm.depth
}

View file

@ -228,6 +228,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} }
// execute the operation // execute the operation
res, err = operation.execute(&pc, in, callContext) res, err = operation.execute(&pc, in, callContext)
if debug {
in.evm.Config.Tracer.CaptureStateAfter(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
}
if err != nil { if err != nil {
break break
} }

View file

@ -39,5 +39,6 @@ type EVMLogger interface {
CaptureExit(output []byte, gasUsed uint64, err error) CaptureExit(output []byte, gasUsed uint64, err error)
// Opcode level // Opcode level
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
} }

View file

@ -72,6 +72,10 @@ func (st *Stack) dup(n int) {
st.push(&st.data[st.len()-n]) st.push(&st.data[st.len()-n])
} }
func (st *Stack) Peek() *uint256.Int {
return st.peek()
}
func (st *Stack) peek() *uint256.Int { func (st *Stack) peek() *uint256.Int {
return &st.data[st.len()-1] return &st.data[st.len()-1]
} }

View file

@ -94,11 +94,13 @@ type Backend interface {
// API is the collection of tracing APIs exposed over the private debugging endpoint. // API is the collection of tracing APIs exposed over the private debugging endpoint.
type API struct { type API struct {
backend Backend backend Backend
scrollTracerWrapper scrollTracerWrapper
} }
// NewAPI creates a new API definition for the tracing methods of the Ethereum service. // NewAPI creates a new API definition for the tracing methods of the Ethereum service.
func NewAPI(backend Backend) *API { func NewAPI(backend Backend, scrollTracerWrapper scrollTracerWrapper) *API {
return &API{backend: backend} return &API{backend: backend, scrollTracerWrapper: scrollTracerWrapper}
} }
// chainContext constructs the context reader which is used by the evm for reading // chainContext constructs the context reader which is used by the evm for reading
@ -1010,16 +1012,22 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), l1DataFee); err != nil { if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), l1DataFee); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err) return nil, fmt.Errorf("tracing failed: %w", err)
} }
return tracer.GetResult() return tracer.GetResultWithL1DataFee(l1DataFee)
} }
// APIs return the collection of RPC services the tracer package offers. // APIs return the collection of RPC services the tracer package offers.
func APIs(backend Backend) []rpc.API { func APIs(backend Backend, scrollTracerWrapper scrollTracerWrapper) []rpc.API {
// Append all the local APIs and return // Append all the local APIs and return
return []rpc.API{ return []rpc.API{
{ {
Namespace: "debug", Namespace: "debug",
Service: NewAPI(backend), Service: NewAPI(backend, scrollTracerWrapper),
},
{
Namespace: "scroll",
Version: "1.0",
Service: TraceBlock(NewAPI(backend, scrollTracerWrapper)),
Public: true,
}, },
} }
} }

View file

@ -0,0 +1,112 @@
package tracers
import (
"context"
"errors"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
var errNoScrollTracerWrapper = errors.New("no ScrollTracerWrapper")
type TraceBlock interface {
GetBlockTraceByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (trace *types.BlockTrace, err error)
GetTxBlockTraceOnTopOfBlock(ctx context.Context, tx *types.Transaction, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (*types.BlockTrace, error)
}
type scrollTracerWrapper interface {
CreateTraceEnvAndGetBlockTrace(*params.ChainConfig, core.ChainContext, consensus.Engine, ethdb.Database, *state.StateDB, *types.Block, *types.Block, bool) (*types.BlockTrace, error)
}
// GetBlockTraceByNumberOrHash replays the block and returns the structured BlockTrace by hash or number.
func (api *API) GetBlockTraceByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (trace *types.BlockTrace, err error) {
if api.scrollTracerWrapper == nil {
return nil, errNoScrollTracerWrapper
}
var block *types.Block
if number, ok := blockNrOrHash.Number(); ok {
block, err = api.blockByNumber(ctx, number)
} else if hash, ok := blockNrOrHash.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
} else {
return nil, errors.New("invalid arguments; neither block number nor hash specified")
}
if err != nil {
return nil, err
}
if block.NumberU64() == 0 {
return nil, errors.New("genesis is not traceable")
}
return api.createTraceEnvAndGetBlockTrace(ctx, config, block)
}
func (api *API) GetTxBlockTraceOnTopOfBlock(ctx context.Context, tx *types.Transaction, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (*types.BlockTrace, error) {
if api.scrollTracerWrapper == nil {
return nil, errNoScrollTracerWrapper
}
// Try to retrieve the specified block
var (
err error
block *types.Block
)
if number, ok := blockNrOrHash.Number(); ok {
block, err = api.blockByNumber(ctx, number)
} else if hash, ok := blockNrOrHash.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
} else {
return nil, errors.New("invalid arguments; neither block number nor hash specified")
}
if err != nil {
return nil, err
}
if block.NumberU64() == 0 {
return nil, errors.New("genesis is not traceable")
}
block = types.NewBlockWithHeader(block.Header()).WithBody([]*types.Transaction{tx}, nil)
return api.createTraceEnvAndGetBlockTrace(ctx, config, block)
}
// Make trace environment for current block, and then get the trace for the block.
func (api *API) createTraceEnvAndGetBlockTrace(ctx context.Context, config *TraceConfig, block *types.Block) (*types.BlockTrace, error) {
if config == nil {
config = &TraceConfig{
Config: &logger.Config{
EnableMemory: false,
EnableReturnData: true,
},
}
} else if config.Tracer != nil {
config.Tracer = nil
log.Warn("Tracer params is unsupported")
}
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
if err != nil {
return nil, err
}
reexec := defaultTraceReexec
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, true)
if err != nil {
return nil, err
}
defer release()
chaindb := api.backend.ChainDb()
return api.scrollTracerWrapper.CreateTraceEnvAndGetBlockTrace(api.backend.ChainConfig(), api.chainContext(ctx), api.backend.Engine(), chaindb, statedb, parent, block, true)
}

View file

@ -38,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -213,7 +212,7 @@ func TestTraceCall(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
}) })
defer backend.teardown() defer backend.teardown()
api := NewAPI(backend) api := NewAPI(backend, nil)
var testSuite = []struct { var testSuite = []struct {
blockNumber rpc.BlockNumber blockNumber rpc.BlockNumber
call ethapi.TransactionArgs call ethapi.TransactionArgs
@ -231,7 +230,7 @@ func TestTraceCall(t *testing.T) {
}, },
config: nil, config: nil,
expectErr: nil, expectErr: nil,
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}`,
}, },
// Standard JSON trace upon the head, plain transfer. // Standard JSON trace upon the head, plain transfer.
{ {
@ -243,7 +242,7 @@ func TestTraceCall(t *testing.T) {
}, },
config: nil, config: nil,
expectErr: nil, expectErr: nil,
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}`,
}, },
// Standard JSON trace upon the non-existent block, error expects // Standard JSON trace upon the non-existent block, error expects
{ {
@ -267,7 +266,7 @@ func TestTraceCall(t *testing.T) {
}, },
config: nil, config: nil,
expectErr: nil, expectErr: nil,
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}`,
}, },
// Tracing on 'pending' should fail: // Tracing on 'pending' should fail:
{ {
@ -292,7 +291,8 @@ func TestTraceCall(t *testing.T) {
expectErr: nil, expectErr: nil,
expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[ expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[
{"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]}, {"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]},
{"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`, {"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}],
"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}`,
}, },
} }
for i, testspec := range testSuite { for i, testspec := range testSuite {
@ -310,11 +310,11 @@ func TestTraceCall(t *testing.T) {
t.Errorf("test %d: expect no error, got %v", i, err) t.Errorf("test %d: expect no error, got %v", i, err)
continue continue
} }
var have *logger.ExecutionResult var have *types.ExecutionResult
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
t.Errorf("test %d: failed to unmarshal result %v", i, err) t.Errorf("test %d: failed to unmarshal result %v", i, err)
} }
var want *logger.ExecutionResult var want *types.ExecutionResult
if err := json.Unmarshal([]byte(testspec.expect), &want); err != nil { if err := json.Unmarshal([]byte(testspec.expect), &want); err != nil {
t.Errorf("test %d: failed to unmarshal result %v", i, err) t.Errorf("test %d: failed to unmarshal result %v", i, err)
} }
@ -348,20 +348,20 @@ func TestTraceTransaction(t *testing.T) {
target = tx.Hash() target = tx.Hash()
}) })
defer backend.chain.Stop() defer backend.chain.Stop()
api := NewAPI(backend) api := NewAPI(backend, nil)
result, err := api.TraceTransaction(context.Background(), target, nil) result, err := api.TraceTransaction(context.Background(), target, nil)
if err != nil { if err != nil {
t.Errorf("Failed to trace transaction %v", err) t.Errorf("Failed to trace transaction %v", err)
} }
var have *logger.ExecutionResult var have *types.ExecutionResult
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
t.Errorf("failed to unmarshal result %v", err) t.Errorf("failed to unmarshal result %v", err)
} }
if !reflect.DeepEqual(have, &logger.ExecutionResult{ if !reflect.DeepEqual(have, &types.ExecutionResult{
Gas: params.TxGas, Gas: params.TxGas,
Failed: false, Failed: false,
ReturnValue: "", ReturnValue: "",
StructLogs: []logger.StructLogRes{}, StructLogs: []types.StructLogRes{},
}) { }) {
t.Error("Transaction tracing result is different") t.Error("Transaction tracing result is different")
} }
@ -398,7 +398,7 @@ func TestTraceBlock(t *testing.T) {
txHash = tx.Hash() txHash = tx.Hash()
}) })
defer backend.chain.Stop() defer backend.chain.Stop()
api := NewAPI(backend) api := NewAPI(backend, nil)
var testSuite = []struct { var testSuite = []struct {
blockNumber rpc.BlockNumber blockNumber rpc.BlockNumber
@ -414,7 +414,7 @@ func TestTraceBlock(t *testing.T) {
// Trace head block // Trace head block
{ {
blockNumber: rpc.BlockNumber(genBlocks), blockNumber: rpc.BlockNumber(genBlocks),
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}}]`, txHash),
}, },
// Trace non-existent block // Trace non-existent block
{ {
@ -424,12 +424,12 @@ func TestTraceBlock(t *testing.T) {
// Trace latest block // Trace latest block
{ {
blockNumber: rpc.LatestBlockNumber, blockNumber: rpc.LatestBlockNumber,
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}}]`, txHash),
}, },
// Trace pending block // Trace pending block
{ {
blockNumber: rpc.PendingBlockNumber, blockNumber: rpc.PendingBlockNumber,
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}}]`, txHash),
}, },
} }
for i, tc := range testSuite { for i, tc := range testSuite {
@ -487,7 +487,7 @@ func TestTracingWithOverrides(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
}) })
defer backend.chain.Stop() defer backend.chain.Stop()
api := NewAPI(backend) api := NewAPI(backend, nil)
randomAccounts := newAccounts(3) randomAccounts := newAccounts(3)
type res struct { type res struct {
Gas int Gas int
@ -851,9 +851,9 @@ func TestTraceChain(t *testing.T) {
}) })
backend.refHook = func() { ref.Add(1) } backend.refHook = func() { ref.Add(1) }
backend.relHook = func() { rel.Add(1) } backend.relHook = func() { rel.Add(1) }
api := NewAPI(backend) api := NewAPI(backend, nil)
single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}` single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[],"accountAfter":null,"l1DataFee":"0x0","callTrace":null,"prestateTrace":null}}`
var cases = []struct { var cases = []struct {
start uint64 start uint64
end uint64 end uint64

View file

@ -279,6 +279,10 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (jst *jsTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the Tracer interface to trace an execution fault // CaptureFault implements the Tracer interface to trace an execution fault
func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
if t.err != nil { if t.err != nil {
@ -353,6 +357,10 @@ func (t *jsTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(encoded), t.err return json.RawMessage(encoded), t.err
} }
func (t *jsTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *jsTracer) Stop(err error) { func (t *jsTracer) Stop(err error) {
t.vm.Interrupt(err) t.vm.Interrupt(err)

View file

@ -158,6 +158,10 @@ func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (*AccessListTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
} }

View file

@ -30,6 +30,9 @@ 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/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/codehash"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -75,6 +78,24 @@ type StructLog struct {
Depth int `json:"depth"` Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"` RefundCounter uint64 `json:"refund"`
Err error `json:"-"` Err error `json:"-"`
// scroll-related
ExtraData *types.ExtraData `json:"extraData"`
}
func (s *StructLog) clean() {
s.Memory = s.Memory[:0]
s.Stack = s.Stack[:0]
s.ReturnData = s.ReturnData[:0]
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 // overrides for gencodec
@ -118,12 +139,17 @@ type StructLogger struct {
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
statesAffected map[common.Address]struct{}
createdAccount *types.AccountWrapper
callStackLogInd []int
} }
// NewStructLogger returns a new logger // NewStructLogger returns a new logger
func NewStructLogger(cfg *Config) *StructLogger { func NewStructLogger(cfg *Config) *StructLogger {
logger := &StructLogger{ logger := &StructLogger{
storage: make(map[common.Address]Storage), storage: make(map[common.Address]Storage),
statesAffected: make(map[common.Address]struct{}),
} }
if cfg != nil { if cfg != nil {
logger.cfg = *cfg logger.cfg = *cfg
@ -137,11 +163,26 @@ func (l *StructLogger) Reset() {
l.output = make([]byte, 0) l.output = make([]byte, 0)
l.logs = l.logs[:0] l.logs = l.logs[:0]
l.err = nil l.err = nil
l.statesAffected = make(map[common.Address]struct{})
l.createdAccount = nil
l.callStackLogInd = nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
l.env = env l.env = env
if create {
// notice codeHash is set AFTER CreateTx has exited, so here codeHash is still empty
l.createdAccount = &types.AccountWrapper{
Address: to,
// nonce is 1 after EIP158, so we query it from stateDb
Nonce: env.StateDB.GetNonce(to),
Balance: (*hexutil.Big)(value),
}
}
l.statesAffected[from] = struct{}{}
l.statesAffected[to] = struct{}{}
} }
// CaptureState logs a new structured log message and pushes it out to the environment // CaptureState logs a new structured log message and pushes it out to the environment
@ -208,8 +249,71 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
copy(rdata, rData) copy(rdata, rData)
} }
// create a new snapshot of the EVM. // create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} structLog := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err, nil}
l.logs = append(l.logs, log)
if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
if err := traceStorage(l, scope, structLog.getOrInitExtraData()); err != nil {
log.Error("Failed to trace data", "opcode", op.String(), "err", err)
}
}
execFuncList, ok := OpcodeExecs[op]
if ok {
// execute trace func list.
for _, exec := range execFuncList {
if e := exec(l, scope, structLog.getOrInitExtraData()); e != nil {
log.Error("Failed to trace data", "opcode", op.String(), "err", e)
}
}
}
// for each "calling" op, pick the caller's state
switch op {
case vm.CALL, vm.CALLCODE, vm.STATICCALL, vm.DELEGATECALL, vm.CREATE, vm.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 == vm.CREATE2 && err == nil {
_ = stack.Data()[stackLen-1] // value
offset := stack.Data()[stackLen-2]
size := stack.Data()[stackLen-3]
salt := stack.Data()[stackLen-4]
// `CaptureState` is called **before** memory resizing
// So sometimes we need to auto pad 0.
code := vm.GetData(scope.Memory.Data(), offset.Uint64(), size.Uint64())
codeAndHash := &codeAndHash{code: code}
address := crypto.CreateAddress2(contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
contractHash := l.env.StateDB.GetKeccakCodeHash(address)
if l.env.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != codehash.EmptyKeccakCodeHash) {
extraData := structLog.getOrInitExtraData()
wrappedStatus := getWrappedAccountForAddr(l, address)
extraData.StateList = append(extraData.StateList, wrappedStatus)
l.statesAffected[address] = struct{}{}
}
}
l.logs = append(l.logs, structLog)
}
// codeAndHash is the same as codeAndHash in core/vm/evm.go
type codeAndHash struct {
code []byte
hash common.Hash
}
func (c *codeAndHash) Hash() common.Hash {
if c.hash == (common.Hash{}) {
// when calculating CREATE2 address, we use Keccak256 not Poseidon
c.hash = crypto.Keccak256Hash(c.code)
}
return c.hash
}
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *StructLogger) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault // CaptureFault implements the EVMLogger interface to trace an execution fault
@ -230,12 +334,82 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
} }
func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// the last logged op should be CALL/STATICCALL/CALLCODE/CREATE/CREATE2
lastLogPos := len(l.logs) - 1
log.Debug("mark call stack", "pos", lastLogPos, "op", l.logs[lastLogPos].Op)
l.callStackLogInd = append(l.callStackLogInd, lastLogPos)
// sanity check
if len(l.callStackLogInd) != l.env.Depth() {
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
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) { func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
stackH := len(l.callStackLogInd)
if stackH == 0 {
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 vm.CREATE, vm.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
}
} }
func (l *StructLogger) GetResult() (json.RawMessage, error) { func (l *StructLogger) GetResult() (json.RawMessage, error) {
result, err := l.getResult()
if err != nil {
return nil, err
}
return json.Marshal(result)
}
func (l *StructLogger) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
result, err := l.getResult()
if err != nil {
return nil, err
}
result.L1DataFee = (*hexutil.Big)(l1DataFee)
return json.Marshal(result)
}
func (l *StructLogger) getResult() (*types.ExecutionResult, error) {
// Tracing aborted // Tracing aborted
if l.reason != nil { if l.reason != nil {
return nil, l.reason return nil, l.reason
@ -247,13 +421,12 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
if failed && l.err != vm.ErrExecutionReverted { if failed && l.err != vm.ErrExecutionReverted {
returnVal = "" returnVal = ""
} }
return json.Marshal(&ExecutionResult{ return &types.ExecutionResult{
Gas: l.usedGas, Gas: l.usedGas,
Failed: failed, Failed: failed,
ReturnValue: returnVal, ReturnValue: returnVal,
StructLogs: formatLogs(l.StructLogs()), StructLogs: FormatLogs(l.StructLogs()),
// L1DataFee: (*hexutil.Big)(result.L1DataFee), }, nil
})
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
@ -279,6 +452,19 @@ func (l *StructLogger) Error() error { return l.err }
// Output returns the VM return value captured by the trace. // Output returns the VM return value captured by the trace.
func (l *StructLogger) Output() []byte { return l.output } func (l *StructLogger) Output() []byte { return l.output }
// UpdatedAccounts is used to collect all "touched" accounts
func (l *StructLogger) UpdatedAccounts() map[common.Address]struct{} {
return l.statesAffected
}
// UpdatedStorages is used to collect all "touched" storage slots
func (l *StructLogger) UpdatedStorages() map[common.Address]Storage {
return l.storage
}
// CreatedAccount return the account data in case it is a create tx
func (l *StructLogger) CreatedAccount() *types.AccountWrapper { return l.createdAccount }
// WriteTrace writes a formatted trace to the given writer // WriteTrace writes a formatted trace to the given writer
func WriteTrace(writer io.Writer, logs []StructLog) { func WriteTrace(writer io.Writer, logs []StructLog) {
for _, log := range logs { for _, log := range logs {
@ -381,6 +567,9 @@ func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
} }
func (t *mdLogger) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
} }
@ -399,37 +588,11 @@ func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
func (*mdLogger) CaptureTxEnd(restGas uint64) {} func (*mdLogger) CaptureTxEnd(restGas uint64) {}
// ExecutionResult groups all structured logs emitted by the EVM // FormatLogs formats EVM returned structured logs for json output
// while replaying a transaction in debug mode as well as transaction func FormatLogs(logs []StructLog) []types.StructLogRes {
// execution status, the amount of gas used and the return value formatted := make([]types.StructLogRes, len(logs))
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
}
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
ReturnData string `json:"returnData,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
RefundCounter uint64 `json:"refund,omitempty"`
}
// formatLogs formats EVM returned structured logs for json output
func formatLogs(logs []StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs { for index, trace := range logs {
formatted[index] = StructLogRes{ formatted[index] = types.StructLogRes{
Pc: trace.Pc, Pc: trace.Pc,
Op: trace.Op.String(), Op: trace.Op.String(),
Gas: trace.Gas, Gas: trace.Gas,

View file

@ -78,6 +78,10 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
l.encoder.Encode(log) l.encoder.Encode(log)
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (l *JSONLogger) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureEnd is triggered at end of execution. // CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
type endLog struct { type endLog struct {

View file

@ -0,0 +1,137 @@
package logger
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)
type traceFunc func(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error
var (
// OpcodeExecs the map to load opcodes' trace funcs.
OpcodeExecs = map[vm.OpCode][]traceFunc{
vm.CALL: {traceToAddressCode, traceLastNAddressCode(1), traceContractAccount, traceLastNAddressAccount(1)}, // contract account is the caller, stack.nth_last(1) is the callee's address
vm.CALLCODE: {traceToAddressCode, traceLastNAddressCode(1), traceContractAccount, traceLastNAddressAccount(1)}, // contract account is the caller, stack.nth_last(1) is the callee's address
vm.DELEGATECALL: {traceToAddressCode, traceLastNAddressCode(1)},
vm.STATICCALL: {traceToAddressCode, traceLastNAddressCode(1), traceLastNAddressAccount(1)},
vm.CREATE: {}, // caller is already recorded in ExtraData.Caller, callee is recorded in CaptureEnter&CaptureExit
vm.CREATE2: {}, // caller is already recorded in ExtraData.Caller, callee is recorded in CaptureEnter&CaptureExit
vm.SLOAD: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag
vm.SSTORE: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag
vm.SELFDESTRUCT: {traceContractAccount, traceLastNAddressAccount(0)},
vm.SELFBALANCE: {traceContractAccount},
vm.BALANCE: {traceLastNAddressAccount(0)},
vm.EXTCODEHASH: {traceLastNAddressAccount(0)},
vm.CODESIZE: {traceContractCode},
vm.CODECOPY: {traceContractCode},
vm.EXTCODESIZE: {traceLastNAddressCode(0)},
vm.EXTCODECOPY: {traceLastNAddressCode(0)},
}
)
// traceToAddressCode gets tx.to addresss code
func traceToAddressCode(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
if l.env.To == nil {
return nil
}
code := l.env.StateDB.GetCode(*l.env.To)
extraData.CodeList = append(extraData.CodeList, hexutil.Encode(code))
return nil
}
// traceLastNAddressCode
func traceLastNAddressCode(n int) traceFunc {
return func(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
stack := scope.Stack
stackData := stack.Data()
stackLen := len(stackData)
if stackLen <= n {
return nil
}
address := common.Address(stackData[stackLen-1-n].Bytes20())
code := l.env.StateDB.GetCode(address)
extraData.CodeList = append(extraData.CodeList, hexutil.Encode(code))
l.statesAffected[address] = struct{}{}
return nil
}
}
// traceContractCode gets the contract's code
func traceContractCode(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
code := l.env.StateDB.GetCode(scope.Contract.Address())
extraData.CodeList = append(extraData.CodeList, hexutil.Encode(code))
return nil
}
// traceStorage get contract's storage at storage_address
func traceStorage(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
if len(scope.Stack.Data()) == 0 {
return nil
}
key := common.Hash(scope.Stack.Peek().Bytes32())
storage := getWrappedAccountForStorage(l, scope.Contract.Address(), key)
extraData.StateList = append(extraData.StateList, storage)
return nil
}
// traceContractAccount gets the contract's account
func traceContractAccount(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
// Get account state.
state := getWrappedAccountForAddr(l, scope.Contract.Address())
extraData.StateList = append(extraData.StateList, state)
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 *vm.ScopeContext, extraData *types.ExtraData) error {
stack := scope.Stack
stackData := stack.Data()
stackLen := len(stackData)
if stackLen <= n {
return nil
}
address := common.Address(stackData[stackLen-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 getWrappedAccountForStorage(l *StructLogger, address common.Address, key common.Hash) *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),
Storage: &types.StorageWrapper{
Key: key.String(),
Value: l.env.StateDB.GetState(address, key).String(),
},
}
}
func getCodeForAddr(l *StructLogger, address common.Address) []byte {
return l.env.StateDB.GetCode(address)
}

View file

@ -90,6 +90,10 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *fourByteTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted // Skip if tracing was interrupted
@ -121,6 +125,10 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
return res, t.reason return res, t.reason
} }
func (t *fourByteTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *fourByteTracer) Stop(err error) { func (t *fourByteTracer) Stop(err error) {
t.reason = err t.reason = err

View file

@ -98,36 +98,44 @@ type callFrameMarshaling struct {
Output hexutil.Bytes Output hexutil.Bytes
} }
type callTracer struct { type CallTracer struct {
noopTracer noopTracer
callstack []callFrame callstack []callFrame
config callTracerConfig config CallTracerConfig
gasLimit uint64 gasLimit uint64
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
} }
type callTracerConfig struct { type CallTracerConfig struct {
OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
WithLog bool `json:"withLog"` // If true, call tracer will collect event logs WithLog bool `json:"withLog"` // If true, call tracer will collect event logs
} }
func NewCallTracerWithConfig(ctx *tracers.Context, config CallTracerConfig) (tracers.Tracer, error) {
return newCallTracerWithConfig(ctx, config)
}
// newCallTracer returns a native go tracer which tracks // newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger. // call frames of a tx, and implements vm.EVMLogger.
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
var config callTracerConfig var config CallTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err return nil, err
} }
} }
return newCallTracerWithConfig(ctx, config)
}
func newCallTracerWithConfig(ctx *tracers.Context, config CallTracerConfig) (tracers.Tracer, error) {
// First callframe contains tx context info // First callframe contains tx context info
// and is populated on start and end. // and is populated on start and end.
return &callTracer{callstack: make([]callFrame, 1), config: config}, nil return &CallTracer{callstack: make([]callFrame, 1), config: config}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.
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) {
toCopy := to toCopy := to
t.callstack[0] = callFrame{ t.callstack[0] = callFrame{
Type: vm.CALL, Type: vm.CALL,
@ -143,12 +151,12 @@ func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad
} }
// 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, err error) { func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.callstack[0].processOutput(output, err) t.callstack[0].processOutput(output, err)
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. // CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *CallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// skip if the previous op caused an error // skip if the previous op caused an error
if err != nil { if err != nil {
return return
@ -193,8 +201,12 @@ func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *CallTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.config.OnlyTopCall { if t.config.OnlyTopCall {
return return
} }
@ -217,7 +229,7 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
// CaptureExit is called when EVM exits a scope, even if the scope didn't // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
if t.config.OnlyTopCall { if t.config.OnlyTopCall {
return return
} }
@ -235,11 +247,11 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
} }
func (t *callTracer) CaptureTxStart(gasLimit uint64) { func (t *CallTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit t.gasLimit = gasLimit
} }
func (t *callTracer) CaptureTxEnd(restGas uint64) { func (t *CallTracer) CaptureTxEnd(restGas uint64) {
t.callstack[0].GasUsed = t.gasLimit - restGas t.callstack[0].GasUsed = t.gasLimit - restGas
if t.config.WithLog { if t.config.WithLog {
// Logs are not emitted when the call fails // Logs are not emitted when the call fails
@ -249,7 +261,7 @@ func (t *callTracer) CaptureTxEnd(restGas uint64) {
// GetResult returns the json-encoded nested list of call traces, and any // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) { func (t *CallTracer) GetResult() (json.RawMessage, error) {
if len(t.callstack) != 1 { if len(t.callstack) != 1 {
return nil, errors.New("incorrect number of top-level calls") return nil, errors.New("incorrect number of top-level calls")
} }
@ -261,8 +273,12 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(res), t.reason return json.RawMessage(res), t.reason
} }
func (t *CallTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *callTracer) Stop(err error) { func (t *CallTracer) Stop(err error) {
t.reason = err t.reason = err
t.interrupt.Store(true) t.interrupt.Store(true)
} }

View file

@ -108,7 +108,7 @@ type flatCallResultMarshaling struct {
// flatCallTracer reports call frame information of a tx in a flat format, i.e. // flatCallTracer reports call frame information of a tx in a flat format, i.e.
// as opposed to the nested format of `callTracer`. // as opposed to the nested format of `callTracer`.
type flatCallTracer struct { type flatCallTracer struct {
tracer *callTracer tracer *CallTracer
config flatCallTracerConfig config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data ctx *tracers.Context // Holds tracer context data
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
@ -135,7 +135,7 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
if err != nil { if err != nil {
return nil, err return nil, err
} }
t, ok := tracer.(*callTracer) t, ok := tracer.(*CallTracer)
if !ok { if !ok {
return nil, errors.New("internal error: embedded tracer has wrong type") return nil, errors.New("internal error: embedded tracer has wrong type")
} }
@ -161,6 +161,10 @@ func (t *flatCallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err) t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err)
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *flatCallTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the EVMLogger interface to trace an execution fault. // CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *flatCallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *flatCallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err) t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err)
@ -227,6 +231,10 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
return res, t.reason return res, t.reason
} }
func (t *flatCallTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *flatCallTracer) Stop(err error) { func (t *flatCallTracer) Stop(err error) {
t.tracer.Stop(err) t.tracer.Stop(err)

View file

@ -29,13 +29,18 @@ func init() {
tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false) tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false)
} }
// muxTracer is a go implementation of the Tracer interface which // MuxTracer is a go implementation of the Tracer interface which
// runs multiple tracers in one go. // runs multiple tracers in one go.
type muxTracer struct { type MuxTracer struct {
names []string names []string
tracers []tracers.Tracer tracers []tracers.Tracer
} }
func (mt *MuxTracer) Append(name string, tracer tracers.Tracer) {
mt.names = append(mt.names, name)
mt.tracers = append(mt.tracers, tracer)
}
// newMuxTracer returns a new mux tracer. // newMuxTracer returns a new mux tracer.
func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
var config map[string]json.RawMessage var config map[string]json.RawMessage
@ -55,39 +60,43 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er
names = append(names, k) names = append(names, k)
} }
return &muxTracer{names: names, tracers: objects}, nil return &MuxTracer{names: names, tracers: objects}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *muxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *MuxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureStart(env, from, to, create, input, gas, value) t.CaptureStart(env, from, to, create, input, gas, value)
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (t *MuxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureEnd(output, gasUsed, err) t.CaptureEnd(output, gasUsed, err)
} }
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. // CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *muxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *MuxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) t.CaptureState(pc, op, gas, cost, scope, rData, depth, err)
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *MuxTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the EVMLogger interface to trace an execution fault. // CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *MuxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureFault(pc, op, gas, cost, scope, depth, err) t.CaptureFault(pc, op, gas, cost, scope, depth, err)
} }
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *MuxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureEnter(typ, from, to, input, gas, value) t.CaptureEnter(typ, from, to, input, gas, value)
} }
@ -95,26 +104,26 @@ func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.A
// CaptureExit is called when EVM exits a scope, even if the scope didn't // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *MuxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureExit(output, gasUsed, err) t.CaptureExit(output, gasUsed, err)
} }
} }
func (t *muxTracer) CaptureTxStart(gasLimit uint64) { func (t *MuxTracer) CaptureTxStart(gasLimit uint64) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxStart(gasLimit) t.CaptureTxStart(gasLimit)
} }
} }
func (t *muxTracer) CaptureTxEnd(restGas uint64) { func (t *MuxTracer) CaptureTxEnd(restGas uint64) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxEnd(restGas) t.CaptureTxEnd(restGas)
} }
} }
// GetResult returns an empty json object. // GetResult returns an empty json object.
func (t *muxTracer) GetResult() (json.RawMessage, error) { func (t *MuxTracer) GetResult() (json.RawMessage, error) {
resObject := make(map[string]json.RawMessage) resObject := make(map[string]json.RawMessage)
for i, tt := range t.tracers { for i, tt := range t.tracers {
r, err := tt.GetResult() r, err := tt.GetResult()
@ -130,8 +139,12 @@ func (t *muxTracer) GetResult() (json.RawMessage, error) {
return res, nil return res, nil
} }
func (t *MuxTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *muxTracer) Stop(err error) { func (t *MuxTracer) Stop(err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.Stop(err) t.Stop(err)
} }

View file

@ -54,6 +54,10 @@ func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *noopTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
} }
@ -72,6 +76,10 @@ func (t *noopTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(`{}`), nil return json.RawMessage(`{}`), nil
} }
func (t *noopTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *noopTracer) Stop(err error) { func (t *noopTracer) Stop(err error) {
} }

View file

@ -54,7 +54,7 @@ type accountMarshaling struct {
Code hexutil.Bytes Code hexutil.Bytes
} }
type prestateTracer struct { type PrestateTracer struct {
noopTracer noopTracer
env *vm.EVM env *vm.EVM
pre state pre state
@ -62,25 +62,33 @@ type prestateTracer struct {
create bool create bool
to common.Address to common.Address
gasLimit uint64 // Amount of gas bought for the whole tx gasLimit uint64 // Amount of gas bought for the whole tx
config prestateTracerConfig config PrestateTracerConfig
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
created map[common.Address]bool created map[common.Address]bool
deleted map[common.Address]bool deleted map[common.Address]bool
} }
type prestateTracerConfig struct { type PrestateTracerConfig struct {
DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
} }
func NewPrestateTracerWithConfig(ctx *tracers.Context, config PrestateTracerConfig) (tracers.Tracer, error) {
return newPrestateTracerWithConfig(ctx, config)
}
func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
var config prestateTracerConfig var config PrestateTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err return nil, err
} }
} }
return &prestateTracer{ return newPrestateTracerWithConfig(ctx, config)
}
func newPrestateTracerWithConfig(ctx *tracers.Context, config PrestateTracerConfig) (tracers.Tracer, error) {
return &PrestateTracer{
pre: state{}, pre: state{},
post: state{}, post: state{},
config: config, config: config,
@ -90,7 +98,7 @@ func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *PrestateTracer) 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.create = create t.create = create
t.to = to t.to = to
@ -118,7 +126,7 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // CaptureEnd is called after the call finishes to finalize the tracing.
func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (t *PrestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
if t.config.DiffMode { if t.config.DiffMode {
return return
} }
@ -133,7 +141,7 @@ func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. // CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *PrestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
if err != nil { if err != nil {
return return
} }
@ -179,11 +187,15 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
} }
} }
func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { // CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *PrestateTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
func (t *PrestateTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit t.gasLimit = gasLimit
} }
func (t *prestateTracer) CaptureTxEnd(restGas uint64) { func (t *PrestateTracer) CaptureTxEnd(restGas uint64) {
if !t.config.DiffMode { if !t.config.DiffMode {
return return
} }
@ -248,7 +260,7 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
// GetResult returns the json-encoded nested list of call traces, and any // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) { func (t *PrestateTracer) GetResult() (json.RawMessage, error) {
var res []byte var res []byte
var err error var err error
if t.config.DiffMode { if t.config.DiffMode {
@ -265,15 +277,19 @@ func (t *prestateTracer) GetResult() (json.RawMessage, error) {
return json.RawMessage(res), t.reason return json.RawMessage(res), t.reason
} }
func (t *PrestateTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
panic("not supported")
}
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
func (t *prestateTracer) Stop(err error) { func (t *PrestateTracer) Stop(err error) {
t.reason = err t.reason = err
t.interrupt.Store(true) t.interrupt.Store(true)
} }
// lookupAccount fetches details of an account and adds it to the prestate // lookupAccount fetches details of an account and adds it to the prestate
// if it doesn't exist there. // if it doesn't exist there.
func (t *prestateTracer) lookupAccount(addr common.Address) { func (t *PrestateTracer) lookupAccount(addr common.Address) {
if _, ok := t.pre[addr]; ok { if _, ok := t.pre[addr]; ok {
return return
} }
@ -289,7 +305,7 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
// lookupStorage fetches the requested storage slot and adds // lookupStorage fetches the requested storage slot and adds
// it to the prestate of the given contract. It assumes `lookupAccount` // it to the prestate of the given contract. It assumes `lookupAccount`
// has been performed on the contract before. // has been performed on the contract before.
func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) { func (t *PrestateTracer) lookupStorage(addr common.Address, key common.Hash) {
if _, ok := t.pre[addr].Storage[key]; ok { if _, ok := t.pre[addr].Storage[key]; ok {
return return
} }

View file

@ -41,6 +41,7 @@ type Context struct {
type Tracer interface { type Tracer interface {
vm.EVMLogger vm.EVMLogger
GetResult() (json.RawMessage, error) GetResult() (json.RawMessage, error)
GetResultWithL1DataFee(*big.Int) (json.RawMessage, error)
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
Stop(err error) Stop(err error)
} }

View file

@ -28,6 +28,7 @@ import (
"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/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -347,6 +348,24 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
return sub, nil return sub, nil
} }
// GetBlockTraceByHash returns the BlockTrace given the block hash.
func (ec *Client) GetBlockTraceByHash(ctx context.Context, blockHash common.Hash) (*types.BlockTrace, error) {
blockTrace := &types.BlockTrace{}
return blockTrace, ec.c.CallContext(ctx, &blockTrace, "scroll_getBlockTraceByNumberOrHash", blockHash)
}
// GetBlockTraceByNumber returns the BlockTrace given the block number.
func (ec *Client) GetBlockTraceByNumber(ctx context.Context, number *big.Int) (*types.BlockTrace, error) {
blockTrace := &types.BlockTrace{}
return blockTrace, ec.c.CallContext(ctx, &blockTrace, "scroll_getBlockTraceByNumberOrHash", toBlockNumArg(number))
}
// GetTxBlockTraceOnTopOfBlock returns the BlockTrace given the tx and block.
func (ec *Client) GetTxBlockTraceOnTopOfBlock(ctx context.Context, tx *types.Transaction, blockNumberOrHash rpc.BlockNumberOrHash, config *tracers.TraceConfig) (*types.BlockTrace, error) {
blockTrace := &types.BlockTrace{}
return blockTrace, ec.c.CallContext(ctx, &blockTrace, "scroll_getTxBlockTraceOnTopOfBlock", tx, blockNumberOrHash, config)
}
// State Access // State Access
// NetworkID returns the network ID for this client. // NetworkID returns the network ID for this client.

View file

@ -18,6 +18,7 @@ package params
import ( import (
"fmt" "fmt"
"runtime/debug"
) )
const ( const (
@ -65,3 +66,14 @@ func VersionWithCommit(gitCommit, gitDate string) string {
} }
return vsn return vsn
} }
var CommitHash = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
return setting.Value
}
}
}
return ""
}()

587
rollup/tracing/tracing.go Normal file
View file

@ -0,0 +1,587 @@
package tracing
import (
"bytes"
"errors"
"fmt"
"runtime"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
"github.com/ethereum/go-ethereum/rollup/withdrawtrie"
// "github.com/ethereum/go-ethereum/trie/zkproof"
)
// TracerWrapper implements ScrollTracerWrapper interface
type TracerWrapper struct{}
// TracerWrapper creates a new TracerWrapper
func NewTracerWrapper() *TracerWrapper {
return &TracerWrapper{}
}
// CreateTraceEnvAndGetBlockTrace wraps the whole block tracing logic for a block
func (tw *TracerWrapper) CreateTraceEnvAndGetBlockTrace(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, parent *types.Block, block *types.Block, commitAfterApply bool) (*types.BlockTrace, error) {
traceEnv, err := CreateTraceEnv(chainConfig, chainContext, engine, chaindb, statedb, parent, block, commitAfterApply)
if err != nil {
return nil, err
}
return traceEnv.GetBlockTrace(block)
}
type TraceEnv struct {
logConfig *logger.Config
commitAfterApply bool
chainConfig *params.ChainConfig
coinbase common.Address
// rMu lock is used to protect txs executed in parallel.
signer types.Signer
state *state.StateDB
blockCtx vm.BlockContext
// pMu lock is used to protect Proofs' read and write mutual exclusion,
// since txs are executed in parallel, so this lock is required.
pMu sync.Mutex
// sMu is required because of txs are executed in parallel,
// this lock is used to protect StorageTrace's read and write mutual exclusion.
sMu sync.Mutex
*types.StorageTrace
TxStorageTraces []*types.StorageTrace
// zktrie tracer is used for zktrie storage to build additional deletion proof
ZkTrieTracer map[string]state.ZktrieProofTracer
ExecutionResults []*types.ExecutionResult
// StartL1QueueIndex is the next L1 message queue index that this block can process.
// Example: If the parent block included QueueIndex=9, then StartL1QueueIndex will
// be 10.
StartL1QueueIndex uint64
}
// Context is the same as Context in eth/tracers/tracers.go
type Context struct {
BlockHash common.Hash
TxIndex int
TxHash common.Hash
}
// txTraceTask is the same as txTraceTask in eth/tracers/api.go
type txTraceTask struct {
statedb *state.StateDB
index int
}
func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *logger.Config, blockCtx vm.BlockContext, startL1QueueIndex uint64, coinbase common.Address, statedb *state.StateDB, rootBefore common.Hash, block *types.Block, commitAfterApply bool) *TraceEnv {
return &TraceEnv{
logConfig: logConfig,
commitAfterApply: commitAfterApply,
chainConfig: chainConfig,
coinbase: coinbase,
signer: types.MakeSigner(chainConfig, block.Number(), block.Time()),
state: statedb,
blockCtx: blockCtx,
StorageTrace: &types.StorageTrace{
RootBefore: rootBefore,
RootAfter: block.Root(),
Proofs: make(map[string][]hexutil.Bytes),
StorageProofs: make(map[string]map[string][]hexutil.Bytes),
},
ZkTrieTracer: make(map[string]state.ZktrieProofTracer),
ExecutionResults: make([]*types.ExecutionResult, block.Transactions().Len()),
TxStorageTraces: make([]*types.StorageTrace, block.Transactions().Len()),
StartL1QueueIndex: startL1QueueIndex,
}
}
func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, parent *types.Block, block *types.Block, commitAfterApply bool) (*TraceEnv, error) {
var coinbase common.Address
var err error
if chainConfig.Scroll.FeeVaultEnabled() {
coinbase = *chainConfig.Scroll.FeeVaultAddress
} else {
coinbase, err = engine.Author(block.Header())
if err != nil {
log.Warn("recover coinbase in CreateTraceEnv fail. using zero-address", "err", err, "blockNumber", block.Header().Number, "headerHash", block.Header().Hash())
}
}
// Collect start queue index, we should always have this value for blocks
// that have been executed.
// FIXME: This value will be incorrect on the signer, since we reuse this
// DB entry to signal which index the worker should continue from.
// Example: Ledger A <-- B <-- C. Block `A` contains up to `QueueIndex=9`.
// For block `B`, the worker skips 10 messages and includes 0.
// `ReadFirstQueueIndexNotInL2Block(B)` will then return `20` on the
// signer to avoid re-processing the same 10 transactions again for
// block `C`.
// `ReadFirstQueueIndexNotInL1Block(B)` will return the correct value
// `10` on follower nodes.
startL1QueueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(chaindb, parent.Hash())
if startL1QueueIndex == nil {
log.Error("missing FirstQueueIndexNotInL2Block for block during trace call", "number", parent.NumberU64(), "hash", parent.Hash())
return nil, fmt.Errorf("missing FirstQueueIndexNotInL2Block for block during trace call: hash=%v, parentHash=%vv", block.Hash(), parent.Hash())
}
env := CreateTraceEnvHelper(
chainConfig,
&logger.Config{
EnableMemory: false,
EnableReturnData: true,
Debug: true,
},
core.NewEVMBlockContext(block.Header(), chainContext, chainConfig, nil),
*startL1QueueIndex,
coinbase,
statedb,
parent.Root(),
block,
commitAfterApply,
)
key := coinbase.String()
if _, exist := env.Proofs[key]; !exist {
proof, err := env.state.GetProof(coinbase)
if err != nil {
log.Error("Proof for coinbase not available", "coinbase", coinbase, "error", err)
// but we still mark the proofs map with nil array
}
env.Proofs[key] = types.WrapProof(proof)
}
return env, nil
}
func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error) {
// Execute all the transaction contained within the block concurrently
var (
txs = block.Transactions()
pend = new(sync.WaitGroup)
jobs = make(chan *txTraceTask, len(txs))
errCh = make(chan error, 1)
)
threads := runtime.NumCPU()
if threads > len(txs) {
threads = len(txs)
}
for th := 0; th < threads; th++ {
pend.Add(1)
go func() {
defer pend.Done()
// Fetch and execute the next transaction trace tasks
for task := range jobs {
if err := env.getTxResult(task.statedb, task.index, block); err != nil {
select {
case errCh <- err:
default:
}
log.Error(
"failed to trace tx",
"txHash", txs[task.index].Hash().String(),
"blockHash", block.Hash().String(),
"blockNumber", block.NumberU64(),
"err", err,
)
}
}
}()
}
// Feed the transactions into the tracers and return
var failed error
for i, tx := range txs {
// Send the trace task over for execution
jobs <- &txTraceTask{statedb: env.state.Copy(), index: i}
// Generate the next state snapshot fast without tracing
msg, _ := core.TransactionToMessage(tx, env.signer, block.BaseFee())
env.state.SetTxContext(tx.Hash(), i)
vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{})
l1DataFee, err := fees.CalculateL1DataFee(tx, env.state)
if err != nil {
failed = err
break
}
if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee); err != nil {
failed = err
break
}
if env.commitAfterApply {
env.state.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
}
}
close(jobs)
pend.Wait()
// after all tx has been traced, collect "deletion proof" for zktrie
for _, tracer := range env.ZkTrieTracer {
delProofs, err := tracer.GetDeletionProofs()
if err != nil {
log.Error("deletion proof failure", "error", err)
} else {
for _, proof := range delProofs {
env.DeletionProofs = append(env.DeletionProofs, proof)
}
}
}
// build dummy per-tx deletion proof
for _, txStorageTrace := range env.TxStorageTraces {
if txStorageTrace != nil {
txStorageTrace.DeletionProofs = env.DeletionProofs
}
}
// If execution failed in between, abort
select {
case err := <-errCh:
return nil, err
default:
if failed != nil {
return nil, failed
}
}
return env.fillBlockTrace(block)
}
func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.Block) error {
tx := block.Transactions()[index]
msg, _ := core.TransactionToMessage(tx, env.signer, block.BaseFee())
from, _ := types.Sender(env.signer, tx)
to := tx.To()
txctx := &Context{
BlockHash: block.TxHash(),
TxIndex: index,
TxHash: tx.Hash(),
}
sender := &types.AccountWrapper{
Address: from,
Nonce: state.GetNonce(from),
Balance: (*hexutil.Big)(state.GetBalance(from)),
KeccakCodeHash: state.GetKeccakCodeHash(from),
PoseidonCodeHash: state.GetPoseidonCodeHash(from),
CodeSize: state.GetCodeSize(from),
}
var receiver *types.AccountWrapper
if to != nil {
receiver = &types.AccountWrapper{
Address: *to,
Nonce: state.GetNonce(*to),
Balance: (*hexutil.Big)(state.GetBalance(*to)),
KeccakCodeHash: state.GetKeccakCodeHash(*to),
PoseidonCodeHash: state.GetPoseidonCodeHash(*to),
CodeSize: state.GetCodeSize(*to),
}
}
structLogger := logger.NewStructLogger(env.logConfig)
tracerContext := tracers.Context{
BlockHash: block.Hash(),
TxIndex: index,
TxHash: tx.Hash(),
}
callTracerConfig := native.CallTracerConfig{
OnlyTopCall: false,
WithLog: true,
}
callTracer, err := native.NewCallTracerWithConfig(&tracerContext, callTracerConfig)
if err != nil {
return fmt.Errorf("failed to create callTracer: %w", err)
}
prestateTracerConfig := native.PrestateTracerConfig{DiffMode: false}
prestateTracer, err := native.NewPrestateTracerWithConfig(&tracerContext, prestateTracerConfig)
if err != nil {
return fmt.Errorf("failed to create prestateTracer: %w", err)
}
tracer := &native.MuxTracer{}
tracer.Append("structLogger", structLogger)
tracer.Append("callTracer", callTracer)
tracer.Append("prestateTracer", prestateTracer)
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), state, env.chainConfig, vm.Config{Tracer: tracer, NoBaseFee: true})
state.SetTxContext(txctx.TxHash, txctx.TxIndex)
// Computes the new state by applying the given message.
l1DataFee, err := fees.CalculateL1DataFee(tx, state)
if err != nil {
return err
}
result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee)
if err != nil {
return err
}
// If the result contains a revert reason, return it.
returnVal := result.Return()
if len(result.Revert()) > 0 {
returnVal = result.Revert()
}
createdAcc := structLogger.CreatedAccount()
var after []*types.AccountWrapper
if to == nil {
if createdAcc == nil {
return errors.New("unexpected tx: address for created contract unavailable")
}
to = &createdAcc.Address
}
// collect affected account after tx being applied
for _, acc := range []common.Address{from, *to, env.coinbase} {
after = append(after, &types.AccountWrapper{
Address: acc,
Nonce: state.GetNonce(acc),
Balance: (*hexutil.Big)(state.GetBalance(acc)),
KeccakCodeHash: state.GetKeccakCodeHash(acc),
PoseidonCodeHash: state.GetPoseidonCodeHash(acc),
CodeSize: state.GetCodeSize(acc),
})
}
txStorageTrace := &types.StorageTrace{
Proofs: make(map[string][]hexutil.Bytes),
StorageProofs: make(map[string]map[string][]hexutil.Bytes),
}
// still we have no state root for per tx, only set the head and tail
if index == 0 {
txStorageTrace.RootBefore = state.GetRootHash()
}
if index == len(block.Transactions())-1 {
txStorageTrace.RootAfter = block.Root()
}
// merge required proof data
proofAccounts := structLogger.UpdatedAccounts()
proofAccounts[vmenv.FeeRecipient()] = struct{}{}
for addr := range proofAccounts {
addrStr := addr.String()
env.pMu.Lock()
checkedProof, existed := env.Proofs[addrStr]
if existed {
txStorageTrace.Proofs[addrStr] = checkedProof
}
env.pMu.Unlock()
if existed {
continue
}
proof, err := state.GetProof(addr)
if err != nil {
log.Error("Proof not available", "address", addrStr, "error", err)
// but we still mark the proofs map with nil array
}
wrappedProof := types.WrapProof(proof)
env.pMu.Lock()
env.Proofs[addrStr] = wrappedProof
txStorageTrace.Proofs[addrStr] = wrappedProof
env.pMu.Unlock()
}
proofStorages := structLogger.UpdatedStorages()
for addr, keys := range proofStorages {
if _, existed := txStorageTrace.StorageProofs[addr.String()]; !existed {
txStorageTrace.StorageProofs[addr.String()] = make(map[string][]hexutil.Bytes)
}
env.sMu.Lock()
trie, err := state.GetStorageTrieForProof(addr)
if err != nil {
// but we still continue to next address
log.Error("Storage trie not available", "error", err, "address", addr)
env.sMu.Unlock()
continue
}
zktrieTracer := state.NewProofTracer(trie)
env.sMu.Unlock()
for key, values := range keys {
addrStr := addr.String()
keyStr := key.String()
isDelete := bytes.Equal(values.Bytes(), common.Hash{}.Bytes())
txm := txStorageTrace.StorageProofs[addrStr]
env.sMu.Lock()
m, existed := env.StorageProofs[addrStr]
if !existed {
m = make(map[string][]hexutil.Bytes)
env.StorageProofs[addrStr] = m
}
if zktrieTracer.Available() && !env.ZkTrieTracer[addrStr].Available() {
env.ZkTrieTracer[addrStr] = state.NewProofTracer(trie)
}
if proof, existed := m[keyStr]; existed {
txm[keyStr] = proof
// still need to touch tracer for deletion
if isDelete && zktrieTracer.Available() {
env.ZkTrieTracer[addrStr].MarkDeletion(key)
}
env.sMu.Unlock()
continue
}
env.sMu.Unlock()
var proof [][]byte
var err error
if zktrieTracer.Available() {
proof, err = state.GetSecureTrieProof(zktrieTracer, key)
} else {
proof, err = state.GetSecureTrieProof(trie, key)
}
if err != nil {
log.Error("Storage proof not available", "error", err, "address", addrStr, "key", keyStr)
// but we still mark the proofs map with nil array
}
wrappedProof := types.WrapProof(proof)
env.sMu.Lock()
txm[keyStr] = wrappedProof
m[keyStr] = wrappedProof
if zktrieTracer.Available() {
if isDelete {
zktrieTracer.MarkDeletion(key)
}
env.ZkTrieTracer[addrStr].Merge(zktrieTracer)
}
env.sMu.Unlock()
}
}
callTrace, err := callTracer.GetResult()
if err != nil {
return fmt.Errorf("failed to get callTracer result: %w", err)
}
prestateTrace, err := prestateTracer.GetResult()
if err != nil {
return fmt.Errorf("failed to get prestateTracer result: %w", err)
}
env.ExecutionResults[index] = &types.ExecutionResult{
From: sender,
To: receiver,
AccountCreated: createdAcc,
AccountsAfter: after,
L1DataFee: (*hexutil.Big)(result.L1DataFee),
Gas: result.UsedGas,
Failed: result.Failed(),
ReturnValue: fmt.Sprintf("%x", returnVal),
StructLogs: logger.FormatLogs(structLogger.StructLogs()),
CallTrace: callTrace,
PrestateTrace: prestateTrace,
}
env.TxStorageTraces[index] = txStorageTrace
return nil
}
// fillBlockTrace content after all the txs are finished running.
func (env *TraceEnv) fillBlockTrace(block *types.Block) (*types.BlockTrace, error) {
statedb := env.state
txs := make([]*types.TransactionData, block.Transactions().Len())
for i, tx := range block.Transactions() {
txs[i] = types.NewTransactionData(tx, block.NumberU64(), block.Time(), env.chainConfig)
}
intrinsicStorageProofs := map[common.Address][]common.Hash{
rcfg.L2MessageQueueAddress: {rcfg.WithdrawTrieRootSlot},
rcfg.L1GasPriceOracleAddress: {
rcfg.L1BaseFeeSlot,
rcfg.OverheadSlot,
rcfg.ScalarSlot,
},
}
for addr, storages := range intrinsicStorageProofs {
if _, existed := env.Proofs[addr.String()]; !existed {
if proof, err := statedb.GetProof(addr); err != nil {
log.Error("Proof for intrinstic address not available", "error", err, "address", addr)
} else {
env.Proofs[addr.String()] = types.WrapProof(proof)
}
}
if _, existed := env.StorageProofs[addr.String()]; !existed {
env.StorageProofs[addr.String()] = make(map[string][]hexutil.Bytes)
}
for _, slot := range storages {
if _, existed := env.StorageProofs[addr.String()][slot.String()]; !existed {
if trie, err := statedb.GetStorageTrieForProof(addr); err != nil {
log.Error("Storage proof for intrinstic address not available", "error", err, "address", addr)
} else if proof, _ := statedb.GetSecureTrieProof(trie, slot); err != nil {
log.Error("Get storage proof for intrinstic address failed", "error", err, "address", addr, "slot", slot)
} else {
env.StorageProofs[addr.String()][slot.String()] = types.WrapProof(proof)
}
}
}
}
var chainID uint64
if env.chainConfig.ChainID != nil {
chainID = env.chainConfig.ChainID.Uint64()
}
blockTrace := &types.BlockTrace{
ChainID: chainID,
Version: params.ArchiveVersion(params.CommitHash),
Coinbase: &types.AccountWrapper{
Address: env.coinbase,
Nonce: statedb.GetNonce(env.coinbase),
Balance: (*hexutil.Big)(statedb.GetBalance(env.coinbase)),
KeccakCodeHash: statedb.GetKeccakCodeHash(env.coinbase),
PoseidonCodeHash: statedb.GetPoseidonCodeHash(env.coinbase),
CodeSize: statedb.GetCodeSize(env.coinbase),
},
Header: block.Header(),
StorageTrace: env.StorageTrace,
ExecutionResults: env.ExecutionResults,
TxStorageTraces: env.TxStorageTraces,
Transactions: txs,
StartL1QueueIndex: env.StartL1QueueIndex,
}
for i, tx := range block.Transactions() {
evmTrace := env.ExecutionResults[i]
// Contract is created.
if tx.To() == nil {
evmTrace.ByteCode = hexutil.Encode(tx.Data())
} else { // contract call be included at this case, specially fallback call's data is empty.
evmTrace.ByteCode = hexutil.Encode(statedb.GetCode(*tx.To()))
// Get tx.to address's code hash.
codeHash := statedb.GetPoseidonCodeHash(*tx.To())
evmTrace.PoseidonCodeHash = &codeHash
}
}
// only zktrie model has the ability to get `mptwitness`.
if env.chainConfig.Scroll.ZktrieEnabled() {
// // we use MPTWitnessNothing by default and do not allow switch among MPTWitnessType atm.
// // MPTWitness will be removed from traces in the future.
// if err := zkproof.FillBlockTraceForMPTWitness(zkproof.MPTWitnessNothing, blockTrace); err != nil {
// log.Error("fill mpt witness fail", "error", err)
// }
}
blockTrace.WithdrawTrieRoot = withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, env.state)
return blockTrace, nil
}

View file

@ -248,7 +248,7 @@ const (
// posSELFDESTRUCT = 2 // posSELFDESTRUCT = 2
) )
func getAccountState(l *types.StructLogRes, pos int) *types.AccountWrapper { func getAccountState(l types.StructLogRes, pos int) *types.AccountWrapper {
if exData := l.ExtraData; exData == nil { if exData := l.ExtraData; exData == nil {
return nil return nil
} else if len(exData.StateList) < pos { } else if len(exData.StateList) < pos {
@ -590,7 +590,7 @@ func (w *zktrieProofWriter) HandleNewState(accountState *types.AccountWrapper) (
} }
} }
func handleLogs(od opOrderer, currentContract common.Address, logs []*types.StructLogRes) { func handleLogs(od opOrderer, currentContract common.Address, logs []types.StructLogRes) {
logStack := []int{0} logStack := []int{0}
contractStack := map[int]common.Address{} contractStack := map[int]common.Address{}
callEnterAddress := currentContract callEnterAddress := currentContract
@ -693,14 +693,14 @@ func handleLogs(od opOrderer, currentContract common.Address, logs []*types.Stru
accountState := getAccountState(sLog, posSSTOREBefore) accountState := getAccountState(sLog, posSSTOREBefore)
od.absorbStorage(accountState, nil) od.absorbStorage(accountState, nil)
case "SSTORE": case "SSTORE":
log.Debug("build SSTORE", "pc", sLog.Pc, "key", sLog.Stack[len(sLog.Stack)-1]) log.Debug("build SSTORE", "pc", sLog.Pc, "key", (*sLog.Stack)[len(*(sLog.Stack))-1])
accountState := copyAccountState(getAccountState(sLog, posSSTOREBefore)) accountState := copyAccountState(getAccountState(sLog, posSSTOREBefore))
// notice the log only provide the value BEFORE store and it is not suitable for our protocol, // notice the log only provide the value BEFORE store and it is not suitable for our protocol,
// here we change it into value AFTER update // here we change it into value AFTER update
before := accountState.Storage before := accountState.Storage
accountState.Storage = &types.StorageWrapper{ accountState.Storage = &types.StorageWrapper{
Key: sLog.Stack[len(sLog.Stack)-1], Key: (*sLog.Stack)[len(*(sLog.Stack))-1],
Value: sLog.Stack[len(sLog.Stack)-2], Value: (*sLog.Stack)[len(*(sLog.Stack))-2],
} }
od.absorbStorage(accountState, before) od.absorbStorage(accountState, before)