From 4ebc036d7547da7c572320a2780856c3deaaa8db Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:26:10 +0800 Subject: [PATCH] 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 --- cmd/utils/flags.go | 7 +- core/block_validator.go | 89 ++++ core/evm.go | 1 + core/state/state_prove.go | 6 +- core/state/statedb.go | 23 + core/types/l2trace.go | 40 +- core/vm/common.go | 5 + core/vm/evm.go | 12 +- core/vm/interpreter.go | 3 + core/vm/logger.go | 1 + core/vm/stack.go | 4 + eth/tracers/api.go | 18 +- eth/tracers/api_blocktrace.go | 112 +++++ eth/tracers/api_test.go | 38 +- eth/tracers/js/goja.go | 8 + eth/tracers/logger/access_list_tracer.go | 4 + eth/tracers/logger/logger.go | 237 +++++++-- eth/tracers/logger/logger_json.go | 4 + eth/tracers/logger/logger_trace.go | 137 ++++++ eth/tracers/native/4byte.go | 8 + eth/tracers/native/call.go | 44 +- eth/tracers/native/call_flat.go | 12 +- eth/tracers/native/mux.go | 39 +- eth/tracers/native/noop.go | 8 + eth/tracers/native/prestate.go | 44 +- eth/tracers/tracers.go | 1 + ethclient/ethclient.go | 19 + params/version.go | 12 + rollup/tracing/tracing.go | 587 +++++++++++++++++++++++ trie/zkproof/writer.go | 10 +- 30 files changed, 1399 insertions(+), 134 deletions(-) create mode 100644 eth/tracers/api_blocktrace.go create mode 100644 eth/tracers/logger/logger_trace.go create mode 100644 rollup/tracing/tracing.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 81b97e14a2..c362c95d11 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -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 } diff --git a/core/block_validator.go b/core/block_validator.go index 7b375c2ef8..ca4b862525 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -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 +} diff --git a/core/evm.go b/core/evm.go index 72345b6c07..05d7f106c2 100644 --- a/core/evm.go +++ b/core/evm.go @@ -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, } diff --git a/core/state/state_prove.go b/core/state/state_prove.go index 17b99681a5..245da17741 100644 --- a/core/state/state_prove.go +++ b/core/state/state_prove.go @@ -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 } diff --git a/core/state/statedb.go b/core/state/statedb.go index 77bcb3f009..7401eabc9e 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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) diff --git a/core/types/l2trace.go b/core/types/l2trace.go index 1c9540ca66..9a0f499a53 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -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"` + 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) @@ -65,24 +66,29 @@ type ExecutionResult struct { // `PoseidonCodeHash` only exists when tx is a contract call. PoseidonCodeHash *common.Hash `json:"poseidonCodeHash,omitempty"` // If it is a contract call, the contract code is returned. - ByteCode string `json:"byteCode,omitempty"` - StructLogs []*StructLogRes `json:"structLogs"` + ByteCode string `json:"byteCode,omitempty"` + + 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 // 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"` - Memory []string `json:"memory,omitempty"` - Storage map[string]string `json:"storage,omitempty"` - RefundCounter uint64 `json:"refund,omitempty"` - ExtraData *ExtraData `json:"extraData,omitempty"` + 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"` + ExtraData *ExtraData `json:"extraData,omitempty"` } // NewStructLogResBasic Basic StructLogRes skeleton, Stack&Memory&Storage&ExtraData are separated from it for GC optimization; diff --git a/core/vm/common.go b/core/vm/common.go index 90ba4a4ad1..a115bcfadc 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -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 { diff --git a/core/vm/evm.go b/core/vm/evm.go index 94c7bdf547..680424283a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -83,9 +83,10 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE - BlobHashes []common.Hash // Provides information for BLOBHASH + 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 } // EVM is the Ethereum Virtual Machine base object and provides @@ -531,3 +532,8 @@ func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) FeeRecipient() common.Address { return evm.Context.Coinbase } + +// Depth returns the environment's depth of the current call stack. +func (evm *EVM) Depth() int { + return evm.depth +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 28da2e80e6..160811fbbb 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -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 } diff --git a/core/vm/logger.go b/core/vm/logger.go index 2667908a84..76467cc4aa 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -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) } diff --git a/core/vm/stack.go b/core/vm/stack.go index e1a957e244..8e4fdb6aa2 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -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] } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index aee189ed73..f8dacbe5ec 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -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, }, } } diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go new file mode 100644 index 0000000000..02e26c6a4f --- /dev/null +++ b/eth/tracers/api_blocktrace.go @@ -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) +} diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 760112994e..a0159a57ca 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -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 diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index d22d140988..1dad946548 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -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) diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 766ee4e4b9..b8b7d133b0 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -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) { } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 595795293b..5b3b3d52c7 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -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), + 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, diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index a2cb4cd9fc..28365e1179 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -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 { diff --git a/eth/tracers/logger/logger_trace.go b/eth/tracers/logger/logger_trace.go new file mode 100644 index 0000000000..c9746bbaeb --- /dev/null +++ b/eth/tracers/logger/logger_trace.go @@ -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) +} diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 5a2c4f9111..20c669d604 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -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 diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 34cf027aca..61ed1dc6ce 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -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) } diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index 266ab99001..df3756acce 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -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) diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index db8ddd6438..97b1ec88f2 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -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) } diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 3beecd8abf..ffc7340953 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -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) { } diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 82451c40a6..7bc8e47bf5 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -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 } diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 7b43b7cf83..228aaebf5e 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -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) } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 83c89a2fd4..83582b6698 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -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. diff --git a/params/version.go b/params/version.go index bcab461a43..c1e30ac5ea 100644 --- a/params/version.go +++ b/params/version.go @@ -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 "" +}() diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go new file mode 100644 index 0000000000..09a336e624 --- /dev/null +++ b/rollup/tracing/tracing.go @@ -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 +} diff --git a/trie/zkproof/writer.go b/trie/zkproof/writer.go index efc51ca244..21b0a481ed 100644 --- a/trie/zkproof/writer.go +++ b/trie/zkproof/writer.go @@ -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)