go-ethereum/core/trace.go
HAOYUatHZ 2f9edf73ca
feat(rollup): add circuits capacity checker (#321)
* add proof for predeployed storages

* reverse inneeded code

* update for mainbranch merging

* add pertx storage trace

* dummy tx proof

* add txstorage trace

* add coinbase storage as trace

* enable (sync) tracing by default

* basic

* init cgo framework

* fix break loop

* integrate the right zkevm version (#323)

* finish rust codes

* use dylib (#325)

* flip

* ?

* use cdylib

* revert

* fix

* apply_tx

* rename

* fixing types

fixing types

fixing types

* clean up

* ExecutionResults (#328)

* filling

* filling

* more

* clean up

* filling

* coinbase

* add

* MPTWitness

* ExecutionResults WIP

* L1fee

L1fee

* sender

* to

* Failed & ReturnValue

* createdAcc & after

* remove MPTWitness

* txStorageTrace

* add FeeRecipient

* add StorageTrace

* fix FFI types

* better logger

* cargo fmt

* fix

* add build tags

* update Makefile

* fix library

* improve ld path

* correctly deal with circuit_capacity_checker returned result

* fix return value

* update cargo (#333)

* update cargo

* update

* update go

* refactor

* raname `circuits capacity checker` to `circuit capacity checker`

* some refactorings

* [Fix] storage proof generation in capacity checker (#348)

* make per-tx storage and deletion proof work

* format

* fix misplaced markdeletion

---------

Co-authored-by: HAOYUatHZ <haoyu@protonmail.com>

* docker (#363)

* update Dockerfile

* build: update go version to 1.19

* update

* fix

* fix

* try

* simplify

* revert go version

update l2geth Dockerfiles

* fix

* fix coinbase

* fix (#369)

* format

* Update version.go

* address comments

* Capacity refactor (#374)

* init

* id

* support multiple instances

* fix id

* fix conflicts

* refactor to use same codes (#379)

* re-init

* WIP

* WIP

* refactor

* go

* minor

* fix storage proof of l1gas price oracle

* move 1

* move 2

* move 3

* move 4

* move 5

move 5

* move 6

move 6

* move 7

* move 8

* move 9

* move 10

* clean up

clean up

---------

Co-authored-by: Ho Vei <noelwei@gmail.com>

* finish basic

* minor

* config capacity check in block_validator (#380)

* init

* done

ref

* fix tests

fix tests

fix tests

fix tests

* add more comments

* apply_block

* improve logs

* cargo fmt

* Capacity big refactor (#383)

* CreateTraceEnv

* WIP

* draft

more

fix

* for test

* fortet

* clean up

* add more comments

* goimports -local github.com/scroll-tech/go-ethereum -w .

* fix typos

* attempt 1

* attempt 2

* attempt 3

* gogogo

* clean up

* fix

* fix

* rename

* minor

* fix

* minor

* minor

* improve doc

* use dedicated `checkCircuitCapacity` flag (#394)

* refactor

* fix

* add lock

* [feat] capacity checking: upgrade libzkp (#395)

* upgrade

* upgrade libzkp

* write RowConsumption (#396)

* write RowConsumption

* name alignments

* revert some formatting

* add lock to CircuitCapacityChecker in BlockValidator

* remove mutex pointer

* improve github workflow

* improve

* store row consumption in mining (#397)

* prepare

* finish

* add more logs

* mark `ApplyBlock` as ready

* update libzkp (#401)

* fix

* Capacity detail (#402)

* fix(block-validation): consider skipping in ValidateL1Messages (#405)

* fix(block-validation): consider skipping in ValidateL1Messages

* fix(block): consider skipping in L1MessageCount

* fix l1 validation tests

* fix NumL1Messages

* fix impl.go return types

fix

* better error handling (#407)

* add

add

* add

* add

* add

* add

* cargo fmt

* add

* update

* add

* WIP

* minor

* gogogo

* gogogo

* fix

* fix

* fix

* cargo clippy

* improve

* improve

* creation lock (#408)

* creation lock

* update

* Debug log (#409)

* add more logs

* more

* more

* fix

* improve

* Update cmd/utils/flags.go

Co-authored-by: Péter Garamvölgyi <peter@scroll.io>

* refactor worker.commit()

* avoid re-calculate

* txpool ccc err handling (#411)

* more explicit error comments

* add more logs

* fix unnecessary commit

* add more logs

* fix `ineffassign`

* add more comments

* log id for `NewCircuitCapacityChecker` (#414)

add log to `NewCircuitCapacityChecker`

* Persist skip info for block where all L1 msgs are skipped (#415)

persist skip info for block where all L1 msgs are skipped

* Update version.go

---------

Co-authored-by: Ho Vei <noelwei@gmail.com>
Co-authored-by: Zhang Zhuo <mycinbrin@gmail.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
2023-08-01 13:52:31 +08:00

493 lines
15 KiB
Go

package core
import (
"bytes"
"errors"
"fmt"
"runtime"
"sync"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/consensus"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/fees"
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
"github.com/scroll-tech/go-ethereum/rollup/withdrawtrie"
"github.com/scroll-tech/go-ethereum/trie/zkproof"
)
type TraceEnv struct {
logConfig *vm.LogConfig
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
}
// 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 CreateTraceEnv(chainConfig *params.ChainConfig, chainContext ChainContext, engine consensus.Engine, statedb *state.StateDB, parent *types.Block, block *types.Block) (*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())
}
}
env := &TraceEnv{
logConfig: &vm.LogConfig{
EnableMemory: false,
EnableReturnData: true,
},
chainConfig: chainConfig,
coinbase: coinbase,
signer: types.MakeSigner(chainConfig, block.Number()),
state: statedb,
blockCtx: NewEVMBlockContext(block.Header(), chainContext, nil),
StorageTrace: &types.StorageTrace{
RootBefore: parent.Root(),
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()),
}
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
}
wrappedProof := make([]hexutil.Bytes, len(proof))
for i, bt := range proof {
wrappedProof[i] = bt
}
env.Proofs[key] = wrappedProof
}
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())
}
}
}()
}
// 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, _ := tx.AsMessage(env.signer, block.BaseFee())
env.state.Prepare(tx.Hash(), i)
vmenv := vm.NewEVM(env.blockCtx, NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{})
l1DataFee, err := fees.CalculateL1DataFee(tx, env.state)
if err != nil {
failed = err
break
}
if _, err = ApplyMessage(vmenv, msg, new(GasPool).AddGas(msg.Gas()), l1DataFee); err != nil {
failed = err
break
}
// we'd better don't finalise
// 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, _ := tx.AsMessage(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),
}
}
tracer := vm.NewStructLogger(env.logConfig)
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(env.blockCtx, NewEVMTxContext(msg), state, env.chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
// Call Prepare to clear out the statedb access list
state.Prepare(txctx.TxHash, txctx.TxIndex)
// Computes the new state by applying the given message.
l1DataFee, err := fees.CalculateL1DataFee(tx, state)
if err != nil {
return fmt.Errorf("tracing failed: %w", err)
}
result, err := ApplyMessage(vmenv, msg, new(GasPool).AddGas(msg.Gas()), l1DataFee)
if err != nil {
return fmt.Errorf("tracing failed: %w", err)
}
// If the result contains a revert reason, return it.
returnVal := result.Return()
if len(result.Revert()) > 0 {
returnVal = result.Revert()
}
createdAcc := tracer.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()
} else if index == len(block.Transactions())-1 {
txStorageTrace.RootAfter = block.Root()
}
// merge required proof data
proofAccounts := tracer.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 := make([]hexutil.Bytes, len(proof))
for i, bt := range proof {
wrappedProof[i] = bt
}
env.pMu.Lock()
env.Proofs[addrStr] = wrappedProof
txStorageTrace.Proofs[addrStr] = wrappedProof
env.pMu.Unlock()
}
proofStorages := tracer.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] = state.NewProofTracer(trie)
}
} else 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 := make([]hexutil.Bytes, len(proof))
for i, bt := range proof {
wrappedProof[i] = bt
}
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()
}
}
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: vm.FormatLogs(tracer.StructLogs()),
}
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(), 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 {
wrappedProof := make([]hexutil.Bytes, len(proof))
for i, bt := range proof {
wrappedProof[i] = bt
}
env.Proofs[addr.String()] = wrappedProof
}
}
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 {
wrappedProof := make([]hexutil.Bytes, len(proof))
for i, bt := range proof {
wrappedProof[i] = bt
}
env.StorageProofs[addr.String()][slot.String()] = wrappedProof
}
}
}
}
blockTrace := &types.BlockTrace{
ChainID: env.chainConfig.ChainID.Uint64(),
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,
}
for i, tx := range block.Transactions() {
evmTrace := env.ExecutionResults[i]
// probably a Contract Call
if len(tx.Data()) != 0 && tx.To() != nil {
evmTrace.ByteCode = hexutil.Encode(statedb.GetCode(*tx.To()))
// Get tx.to address's code hash.
codeHash := statedb.GetPoseidonCodeHash(*tx.To())
evmTrace.PoseidonCodeHash = &codeHash
} else if tx.To() == nil { // Contract is created.
evmTrace.ByteCode = hexutil.Encode(tx.Data())
}
}
// 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
}