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/netutil"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/tracing"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -2032,7 +2034,8 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,13 +19,16 @@ package core
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"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/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/circuitcapacitychecker"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
|
|
@ -37,6 +40,12 @@ type BlockValidator struct {
|
|||
config *params.ChainConfig // Chain configuration options
|
||||
bc *BlockChain // Canonical block chain
|
||||
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
|
||||
|
|
@ -49,6 +58,17 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
|||
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
|
||||
// header's transaction and uncle roots. The headers are assumed to be already
|
||||
// validated at this point.
|
||||
|
|
@ -131,6 +151,27 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -271,3 +312,51 @@ func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 {
|
|||
}
|
||||
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 {
|
||||
return vm.TxContext{
|
||||
Origin: msg.From,
|
||||
To: msg.To,
|
||||
GasPrice: new(big.Int).Set(msg.GasPrice),
|
||||
BlobHashes: msg.BlobHashes,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type TrieProve interface {
|
||||
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
|
||||
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
||||
}
|
||||
|
||||
type ZktrieProofTracer struct {
|
||||
|
|
@ -77,9 +77,9 @@ func (s *StateDB) GetSecureTrieProof(trieProve TrieProve, key common.Hash) ([][]
|
|||
var err error
|
||||
if s.IsUsingZktrie() {
|
||||
key_s, _ := zkt.ToSecureKeyBytes(key.Bytes())
|
||||
err = trieProve.Prove(key_s.Bytes(), 0, &proof)
|
||||
err = trieProve.Prove(key_s.Bytes(), &proof)
|
||||
} else {
|
||||
err = trieProve.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
|
||||
err = trieProve.Prove(crypto.Keccak256(key.Bytes()), &proof)
|
||||
}
|
||||
return proof, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import (
|
|||
"sort"
|
||||
"time"
|
||||
|
||||
zkt "github.com/scroll-tech/zktrie/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"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/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
"github.com/ethereum/go-ethereum/trie/zkproof"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -358,6 +361,26 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) 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.
|
||||
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
|
|
|
|||
|
|
@ -46,10 +46,11 @@ type StorageTrace struct {
|
|||
// while replaying a transaction in debug mode as well as transaction
|
||||
// execution status, the amount of gas used and the return value
|
||||
type ExecutionResult struct {
|
||||
L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"`
|
||||
Gas uint64 `json:"gas"`
|
||||
Failed bool `json:"failed"`
|
||||
ReturnValue string `json:"returnValue"`
|
||||
StructLogs []StructLogRes `json:"structLogs"`
|
||||
|
||||
// Sender's account state (before Tx)
|
||||
From *AccountWrapper `json:"from,omitempty"`
|
||||
// Receiver's account state (before Tx)
|
||||
|
|
@ -66,7 +67,11 @@ type ExecutionResult struct {
|
|||
PoseidonCodeHash *common.Hash `json:"poseidonCodeHash,omitempty"`
|
||||
// If it is a contract call, the contract code is returned.
|
||||
ByteCode string `json:"byteCode,omitempty"`
|
||||
StructLogs []*StructLogRes `json:"structLogs"`
|
||||
|
||||
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
|
||||
|
|
@ -78,9 +83,10 @@ type StructLogRes struct {
|
|||
GasCost uint64 `json:"gasCost"`
|
||||
Depth int `json:"depth"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Stack []string `json:"stack,omitempty"`
|
||||
Memory []string `json:"memory,omitempty"`
|
||||
Storage map[string]string `json:"storage,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"`
|
||||
ExtraData *ExtraData `json:"extraData,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) {
|
|||
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
|
||||
// up to size with zero's. This function is overflow safe.
|
||||
func getData(data []byte, start uint64, size uint64) []byte {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ type BlockContext struct {
|
|||
type TxContext struct {
|
||||
// Message information
|
||||
Origin common.Address // Provides information for ORIGIN
|
||||
To *common.Address // Provides information for TO in trace
|
||||
GasPrice *big.Int // Provides information for GASPRICE
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,5 +39,6 @@ type EVMLogger interface {
|
|||
CaptureExit(output []byte, gasUsed uint64, err error)
|
||||
// Opcode level
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ func (st *Stack) dup(n int) {
|
|||
st.push(&st.data[st.len()-n])
|
||||
}
|
||||
|
||||
func (st *Stack) Peek() *uint256.Int {
|
||||
return st.peek()
|
||||
}
|
||||
|
||||
func (st *Stack) peek() *uint256.Int {
|
||||
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.
|
||||
type API struct {
|
||||
backend Backend
|
||||
|
||||
scrollTracerWrapper scrollTracerWrapper
|
||||
}
|
||||
|
||||
// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
|
||||
func NewAPI(backend Backend) *API {
|
||||
return &API{backend: backend}
|
||||
func NewAPI(backend Backend, scrollTracerWrapper scrollTracerWrapper) *API {
|
||||
return &API{backend: backend, scrollTracerWrapper: scrollTracerWrapper}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
func APIs(backend Backend) []rpc.API {
|
||||
func APIs(backend Backend, scrollTracerWrapper scrollTracerWrapper) []rpc.API {
|
||||
// Append all the local APIs and return
|
||||
return []rpc.API{
|
||||
{
|
||||
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/vm"
|
||||
"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/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -213,7 +212,7 @@ func TestTraceCall(t *testing.T) {
|
|||
b.AddTx(tx)
|
||||
})
|
||||
defer backend.teardown()
|
||||
api := NewAPI(backend)
|
||||
api := NewAPI(backend, nil)
|
||||
var testSuite = []struct {
|
||||
blockNumber rpc.BlockNumber
|
||||
call ethapi.TransactionArgs
|
||||
|
|
@ -231,7 +230,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: 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.
|
||||
{
|
||||
|
|
@ -243,7 +242,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: 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
|
||||
{
|
||||
|
|
@ -267,7 +266,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: 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:
|
||||
{
|
||||
|
|
@ -292,7 +291,8 @@ func TestTraceCall(t *testing.T) {
|
|||
expectErr: nil,
|
||||
expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[
|
||||
{"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 {
|
||||
|
|
@ -310,11 +310,11 @@ func TestTraceCall(t *testing.T) {
|
|||
t.Errorf("test %d: expect no error, got %v", i, err)
|
||||
continue
|
||||
}
|
||||
var have *logger.ExecutionResult
|
||||
var have *types.ExecutionResult
|
||||
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
|
||||
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 {
|
||||
t.Errorf("test %d: failed to unmarshal result %v", i, err)
|
||||
}
|
||||
|
|
@ -348,20 +348,20 @@ func TestTraceTransaction(t *testing.T) {
|
|||
target = tx.Hash()
|
||||
})
|
||||
defer backend.chain.Stop()
|
||||
api := NewAPI(backend)
|
||||
api := NewAPI(backend, nil)
|
||||
result, err := api.TraceTransaction(context.Background(), target, nil)
|
||||
if err != nil {
|
||||
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 {
|
||||
t.Errorf("failed to unmarshal result %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(have, &logger.ExecutionResult{
|
||||
if !reflect.DeepEqual(have, &types.ExecutionResult{
|
||||
Gas: params.TxGas,
|
||||
Failed: false,
|
||||
ReturnValue: "",
|
||||
StructLogs: []logger.StructLogRes{},
|
||||
StructLogs: []types.StructLogRes{},
|
||||
}) {
|
||||
t.Error("Transaction tracing result is different")
|
||||
}
|
||||
|
|
@ -398,7 +398,7 @@ func TestTraceBlock(t *testing.T) {
|
|||
txHash = tx.Hash()
|
||||
})
|
||||
defer backend.chain.Stop()
|
||||
api := NewAPI(backend)
|
||||
api := NewAPI(backend, nil)
|
||||
|
||||
var testSuite = []struct {
|
||||
blockNumber rpc.BlockNumber
|
||||
|
|
@ -414,7 +414,7 @@ func TestTraceBlock(t *testing.T) {
|
|||
// Trace head block
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -424,12 +424,12 @@ func TestTraceBlock(t *testing.T) {
|
|||
// Trace latest block
|
||||
{
|
||||
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
|
||||
{
|
||||
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 {
|
||||
|
|
@ -487,7 +487,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
b.AddTx(tx)
|
||||
})
|
||||
defer backend.chain.Stop()
|
||||
api := NewAPI(backend)
|
||||
api := NewAPI(backend, nil)
|
||||
randomAccounts := newAccounts(3)
|
||||
type res struct {
|
||||
Gas int
|
||||
|
|
@ -851,9 +851,9 @@ func TestTraceChain(t *testing.T) {
|
|||
})
|
||||
backend.refHook = func() { ref.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 {
|
||||
start 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
|
||||
func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
|
||||
if t.err != nil {
|
||||
|
|
@ -353,6 +357,10 @@ func (t *jsTracer) GetResult() (json.RawMessage, error) {
|
|||
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.
|
||||
func (t *jsTracer) Stop(err error) {
|
||||
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) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"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/holiman/uint256"
|
||||
)
|
||||
|
|
@ -75,6 +78,24 @@ type StructLog struct {
|
|||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
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
|
||||
|
|
@ -118,12 +139,17 @@ type StructLogger struct {
|
|||
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
|
||||
statesAffected map[common.Address]struct{}
|
||||
createdAccount *types.AccountWrapper
|
||||
callStackLogInd []int
|
||||
}
|
||||
|
||||
// NewStructLogger returns a new logger
|
||||
func NewStructLogger(cfg *Config) *StructLogger {
|
||||
logger := &StructLogger{
|
||||
storage: make(map[common.Address]Storage),
|
||||
statesAffected: make(map[common.Address]struct{}),
|
||||
}
|
||||
if cfg != nil {
|
||||
logger.cfg = *cfg
|
||||
|
|
@ -137,11 +163,26 @@ func (l *StructLogger) Reset() {
|
|||
l.output = make([]byte, 0)
|
||||
l.logs = l.logs[:0]
|
||||
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.
|
||||
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
|
||||
|
||||
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
|
||||
|
|
@ -208,8 +249,71 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
|
|||
copy(rdata, rData)
|
||||
}
|
||||
// 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}
|
||||
l.logs = append(l.logs, log)
|
||||
structLog := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err, nil}
|
||||
|
||||
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
|
||||
|
|
@ -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) {
|
||||
// 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) {
|
||||
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) {
|
||||
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
|
||||
if l.reason != nil {
|
||||
return nil, l.reason
|
||||
|
|
@ -247,13 +421,12 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
|||
if failed && l.err != vm.ErrExecutionReverted {
|
||||
returnVal = ""
|
||||
}
|
||||
return json.Marshal(&ExecutionResult{
|
||||
return &types.ExecutionResult{
|
||||
Gas: l.usedGas,
|
||||
Failed: failed,
|
||||
ReturnValue: returnVal,
|
||||
StructLogs: formatLogs(l.StructLogs()),
|
||||
// L1DataFee: (*hexutil.Big)(result.L1DataFee),
|
||||
})
|
||||
StructLogs: FormatLogs(l.StructLogs()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
func WriteTrace(writer io.Writer, logs []StructLog) {
|
||||
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) {
|
||||
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) {}
|
||||
|
||||
// ExecutionResult groups all structured logs emitted by the EVM
|
||||
// while replaying a transaction in debug mode as well as transaction
|
||||
// execution status, the amount of gas used and the return value
|
||||
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))
|
||||
// FormatLogs formats EVM returned structured logs for json output
|
||||
func FormatLogs(logs []StructLog) []types.StructLogRes {
|
||||
formatted := make([]types.StructLogRes, len(logs))
|
||||
for index, trace := range logs {
|
||||
formatted[index] = StructLogRes{
|
||||
formatted[index] = types.StructLogRes{
|
||||
Pc: trace.Pc,
|
||||
Op: trace.Op.String(),
|
||||
Gas: trace.Gas,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
|
|||
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.
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
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).
|
||||
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
|
||||
|
|
@ -121,6 +125,10 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
|
|||
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.
|
||||
func (t *fourByteTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
|
|
|
|||
|
|
@ -98,36 +98,44 @@ type callFrameMarshaling struct {
|
|||
Output hexutil.Bytes
|
||||
}
|
||||
|
||||
type callTracer struct {
|
||||
type CallTracer struct {
|
||||
noopTracer
|
||||
callstack []callFrame
|
||||
config callTracerConfig
|
||||
config CallTracerConfig
|
||||
gasLimit uint64
|
||||
interrupt atomic.Bool // Atomic flag to signal execution 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
|
||||
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
|
||||
// call frames of a tx, and implements vm.EVMLogger.
|
||||
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
var config callTracerConfig
|
||||
var config CallTracerConfig
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return newCallTracerWithConfig(ctx, config)
|
||||
}
|
||||
|
||||
func newCallTracerWithConfig(ctx *tracers.Context, config CallTracerConfig) (tracers.Tracer, error) {
|
||||
// First callframe contains tx context info
|
||||
// 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.
|
||||
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
|
||||
t.callstack[0] = callFrame{
|
||||
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.
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
if err != nil {
|
||||
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).
|
||||
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 {
|
||||
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
|
||||
// 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureTxStart(gasLimit uint64) {
|
||||
func (t *CallTracer) CaptureTxStart(gasLimit uint64) {
|
||||
t.gasLimit = gasLimit
|
||||
}
|
||||
|
||||
func (t *callTracer) CaptureTxEnd(restGas uint64) {
|
||||
func (t *CallTracer) CaptureTxEnd(restGas uint64) {
|
||||
t.callstack[0].GasUsed = t.gasLimit - restGas
|
||||
if t.config.WithLog {
|
||||
// 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
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
func (t *CallTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// 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.interrupt.Store(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ type flatCallResultMarshaling struct {
|
|||
// flatCallTracer reports call frame information of a tx in a flat format, i.e.
|
||||
// as opposed to the nested format of `callTracer`.
|
||||
type flatCallTracer struct {
|
||||
tracer *callTracer
|
||||
tracer *CallTracer
|
||||
config flatCallTracerConfig
|
||||
ctx *tracers.Context // Holds tracer context data
|
||||
reason error // Textual reason for the interruption
|
||||
|
|
@ -135,7 +135,7 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, ok := tracer.(*callTracer)
|
||||
t, ok := tracer.(*CallTracer)
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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)
|
||||
|
|
@ -227,6 +231,10 @@ func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
|
|||
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.
|
||||
func (t *flatCallTracer) Stop(err error) {
|
||||
t.tracer.Stop(err)
|
||||
|
|
|
|||
|
|
@ -29,13 +29,18 @@ func init() {
|
|||
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.
|
||||
type muxTracer struct {
|
||||
type MuxTracer struct {
|
||||
names []string
|
||||
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.
|
||||
func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
return &muxTracer{names: names, tracers: objects}, nil
|
||||
return &MuxTracer{names: names, tracers: objects}, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t.CaptureStart(env, from, to, create, input, gas, value)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t.CaptureEnd(output, gasUsed, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
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 {
|
||||
t.CaptureFault(pc, op, gas, cost, scope, depth, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
// 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 {
|
||||
t.CaptureExit(output, gasUsed, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) CaptureTxStart(gasLimit uint64) {
|
||||
func (t *MuxTracer) CaptureTxStart(gasLimit uint64) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureTxStart(gasLimit)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *muxTracer) CaptureTxEnd(restGas uint64) {
|
||||
func (t *MuxTracer) CaptureTxEnd(restGas uint64) {
|
||||
for _, t := range t.tracers {
|
||||
t.CaptureTxEnd(restGas)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
for i, tt := range t.tracers {
|
||||
r, err := tt.GetResult()
|
||||
|
|
@ -130,8 +139,12 @@ func (t *muxTracer) GetResult() (json.RawMessage, error) {
|
|||
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.
|
||||
func (t *muxTracer) Stop(err error) {
|
||||
func (t *MuxTracer) Stop(err error) {
|
||||
for _, t := range t.tracers {
|
||||
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) {
|
||||
}
|
||||
|
||||
// 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).
|
||||
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
|
||||
}
|
||||
|
||||
func (t *noopTracer) GetResultWithL1DataFee(l1DataFee *big.Int) (json.RawMessage, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *noopTracer) Stop(err error) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ type accountMarshaling struct {
|
|||
Code hexutil.Bytes
|
||||
}
|
||||
|
||||
type prestateTracer struct {
|
||||
type PrestateTracer struct {
|
||||
noopTracer
|
||||
env *vm.EVM
|
||||
pre state
|
||||
|
|
@ -62,25 +62,33 @@ type prestateTracer struct {
|
|||
create bool
|
||||
to common.Address
|
||||
gasLimit uint64 // Amount of gas bought for the whole tx
|
||||
config prestateTracerConfig
|
||||
config PrestateTracerConfig
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
created 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
|
||||
}
|
||||
|
||||
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) {
|
||||
var config prestateTracerConfig
|
||||
var config PrestateTracerConfig
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &prestateTracer{
|
||||
return newPrestateTracerWithConfig(ctx, config)
|
||||
}
|
||||
|
||||
func newPrestateTracerWithConfig(ctx *tracers.Context, config PrestateTracerConfig) (tracers.Tracer, error) {
|
||||
return &PrestateTracer{
|
||||
pre: state{},
|
||||
post: state{},
|
||||
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.
|
||||
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.create = create
|
||||
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.
|
||||
func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (t *PrestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
if t.config.DiffMode {
|
||||
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.
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
|
||||
func (t *PrestateTracer) CaptureTxEnd(restGas uint64) {
|
||||
if !t.config.DiffMode {
|
||||
return
|
||||
}
|
||||
|
|
@ -248,7 +260,7 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
|
|||
|
||||
// GetResult returns the json-encoded nested list of call traces, and any
|
||||
// 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 err error
|
||||
if t.config.DiffMode {
|
||||
|
|
@ -265,15 +277,19 @@ func (t *prestateTracer) GetResult() (json.RawMessage, error) {
|
|||
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.
|
||||
func (t *prestateTracer) Stop(err error) {
|
||||
func (t *PrestateTracer) Stop(err error) {
|
||||
t.reason = err
|
||||
t.interrupt.Store(true)
|
||||
}
|
||||
|
||||
// lookupAccount fetches details of an account and adds it to the prestate
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
|
|
@ -289,7 +305,7 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
|
|||
// lookupStorage fetches the requested storage slot and adds
|
||||
// it to the prestate of the given contract. It assumes `lookupAccount`
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ type Context struct {
|
|||
type Tracer interface {
|
||||
vm.EVMLogger
|
||||
GetResult() (json.RawMessage, error)
|
||||
GetResultWithL1DataFee(*big.Int) (json.RawMessage, error)
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
Stop(err error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ 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/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -347,6 +348,24 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
|
|||
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
|
||||
|
||||
// NetworkID returns the network ID for this client.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package params
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -65,3 +66,14 @@ func VersionWithCommit(gitCommit, gitDate string) string {
|
|||
}
|
||||
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
|
||||
)
|
||||
|
||||
func getAccountState(l *types.StructLogRes, pos int) *types.AccountWrapper {
|
||||
func getAccountState(l types.StructLogRes, pos int) *types.AccountWrapper {
|
||||
if exData := l.ExtraData; exData == nil {
|
||||
return nil
|
||||
} 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}
|
||||
contractStack := map[int]common.Address{}
|
||||
callEnterAddress := currentContract
|
||||
|
|
@ -693,14 +693,14 @@ func handleLogs(od opOrderer, currentContract common.Address, logs []*types.Stru
|
|||
accountState := getAccountState(sLog, posSSTOREBefore)
|
||||
od.absorbStorage(accountState, nil)
|
||||
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))
|
||||
// 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
|
||||
before := accountState.Storage
|
||||
accountState.Storage = &types.StorageWrapper{
|
||||
Key: sLog.Stack[len(sLog.Stack)-1],
|
||||
Value: sLog.Stack[len(sLog.Stack)-2],
|
||||
Key: (*sLog.Stack)[len(*(sLog.Stack))-1],
|
||||
Value: (*sLog.Stack)[len(*(sLog.Stack))-2],
|
||||
}
|
||||
od.absorbStorage(accountState, before)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue