mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
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:
parent
2829e66aea
commit
4ebc036d75
30 changed files with 1399 additions and 134 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -66,7 +67,11 @@ type ExecutionResult struct {
|
||||||
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
|
||||||
|
|
@ -78,9 +83,10 @@ type StructLogRes struct {
|
||||||
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"`
|
||||||
|
Storage *map[string]string `json:"storage,omitempty"`
|
||||||
RefundCounter uint64 `json:"refund,omitempty"`
|
RefundCounter uint64 `json:"refund,omitempty"`
|
||||||
ExtraData *ExtraData `json:"extraData,omitempty"`
|
ExtraData *ExtraData `json:"extraData,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ type BlockContext struct {
|
||||||
type TxContext struct {
|
type TxContext struct {
|
||||||
// Message information
|
// Message information
|
||||||
Origin common.Address // Provides information for ORIGIN
|
Origin common.Address // Provides information for ORIGIN
|
||||||
|
To *common.Address // Provides information for TO in trace
|
||||||
GasPrice *big.Int // Provides information for GASPRICE
|
GasPrice *big.Int // Provides information for GASPRICE
|
||||||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
112
eth/tracers/api_blocktrace.go
Normal file
112
eth/tracers/api_blocktrace.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
137
eth/tracers/logger/logger_trace.go
Normal file
137
eth/tracers/logger/logger_trace.go
Normal 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 address’s 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
587
rollup/tracing/tracing.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue