feat: add replay blockResult API (#139)

* replay trace

* replay trace

* add sdk

* fix trace test case

* fix bug

* Update eth/tracers/api_blockResult.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update eth/tracers/api_blockResult.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update eth/tracers/api_blockResult.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update eth/tracers/api_blockResult.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* add comments

* fail the rpc call if get error

* adjust channel length

* fix bug

* fix bug

* remove redundant codes in worker

* add test case

* fix bug

* fix goimports

* Update eth/tracers/api_blockResult.go

Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>

* fix comments

* Update ethclient/ethclient.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update ethclient/ethclient.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* rm coinbase api in miner

* fix comment

* Update eth/tracers/api.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* fix comment

Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
Co-authored-by: Haichen Shen <shenhaichen@gmail.com>
This commit is contained in:
maskpp 2022-09-20 14:30:57 +08:00 committed by GitHub
parent edbb595e66
commit 15391eeaf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 680 additions and 370 deletions

View file

@ -215,7 +215,6 @@ func dumpConfig(ctx *cli.Context) error {
func applyTraceConfig(ctx *cli.Context, cfg *ethconfig.Config) {
subCfg := debug.ConfigTrace(ctx)
cfg.TraceCacheLimit = subCfg.TraceCacheLimit
cfg.MPTWitness = subCfg.MPTWitness
}

View file

@ -31,7 +31,6 @@ import (
lru "github.com/hashicorp/golang-lru"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/mclock"
"github.com/scroll-tech/go-ethereum/common/prque"
"github.com/scroll-tech/go-ethereum/consensus"
@ -94,7 +93,6 @@ const (
maxFutureBlocks = 256
maxTimeFutureBlocks = 30
TriesInMemory = 128
blockResultCacheLimit = 128
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
//
@ -134,7 +132,6 @@ type CacheConfig struct {
TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
Preimages bool // Whether to store preimage of trie key to the disk
TraceCacheLimit int
MPTWitness int // How to generate witness data for mpt circuit, 0: nothing, 1: natural
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
@ -148,7 +145,6 @@ var defaultCacheConfig = &CacheConfig{
TrieTimeLimit: 5 * time.Minute,
SnapshotLimit: 256,
SnapshotWait: true,
TraceCacheLimit: 32,
MPTWitness: int(zkproof.MPTWitnessNothing),
}
@ -206,7 +202,6 @@ type BlockChain struct {
blockCache *lru.Cache // Cache for the most recent entire blocks
txLookupCache *lru.Cache // Cache for the most recent transaction lookup data.
futureBlocks *lru.Cache // future blocks are blocks added for later processing
blockResultCache *lru.Cache // Cache for the most recent block results.
wg sync.WaitGroup //
quit chan struct{} // shutdown signal, closed in Stop.
@ -235,10 +230,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
blockCache, _ := lru.New(blockCacheLimit)
txLookupCache, _ := lru.New(txLookupCacheLimit)
futureBlocks, _ := lru.New(maxFutureBlocks)
blockResultCache, _ := lru.New(blockResultCacheLimit)
if cacheConfig.TraceCacheLimit != 0 {
blockResultCache, _ = lru.New(cacheConfig.TraceCacheLimit)
}
// override snapshot setting
if chainConfig.Zktrie && cacheConfig.SnapshotLimit > 0 {
log.Warn("snapshot has been disabled by zktrie")
@ -265,7 +256,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
blockCache: blockCache,
txLookupCache: txLookupCache,
futureBlocks: futureBlocks,
blockResultCache: blockResultCache,
engine: engine,
vmConfig: vmConfig,
}
@ -1202,17 +1192,17 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
}
// WriteBlockWithState writes the block and all associated state to the database.
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, evmTraces []*types.ExecutionResult, storageTrace *types.StorageTrace, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
if !bc.chainmu.TryLock() {
return NonStatTy, errInsertionInterrupted
}
defer bc.chainmu.Unlock()
return bc.writeBlockWithState(block, receipts, logs, evmTraces, storageTrace, state, emitHeadEvent)
return bc.writeBlockWithState(block, receipts, logs, state, emitHeadEvent)
}
// writeBlockWithState writes the block and all associated state to the database,
// but is expects the chain mutex to be held.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, evmTraces []*types.ExecutionResult, storageTrace *types.StorageTrace, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
if bc.insertStopped() {
return NonStatTy, errInsertionInterrupted
}
@ -1333,15 +1323,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
}
bc.futureBlocks.Remove(block.Hash())
// Fill blockResult content
var blockResult *types.BlockResult
if evmTraces != nil {
blockResult = bc.writeBlockResult(state, block, evmTraces, storageTrace)
bc.blockResultCache.Add(block.Hash(), blockResult)
}
if status == CanonStatTy {
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs, BlockResult: blockResult})
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if len(logs) > 0 {
bc.logsFeed.Send(logs)
}
@ -1359,40 +1342,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
return status, nil
}
// Fill blockResult content
func (bc *BlockChain) writeBlockResult(state *state.StateDB, block *types.Block, evmTraces []*types.ExecutionResult, storageTrace *types.StorageTrace) *types.BlockResult {
blockResult := &types.BlockResult{
ExecutionResults: evmTraces,
StorageTrace: storageTrace,
}
coinbase := types.AccountWrapper{
Address: block.Coinbase(),
Nonce: state.GetNonce(block.Coinbase()),
Balance: (*hexutil.Big)(state.GetBalance(block.Coinbase())),
CodeHash: state.GetCodeHash(block.Coinbase()),
}
blockResult.BlockTrace = types.NewTraceBlock(bc.chainConfig, block, &coinbase)
for i, tx := range block.Transactions() {
evmTrace := blockResult.ExecutionResults[i]
// Contract is called
if len(tx.Data()) != 0 && tx.To() != nil {
evmTrace.ByteCode = hexutil.Encode(state.GetCode(*tx.To()))
// Get tx.to address's code hash.
codeHash := state.GetCodeHash(*tx.To())
evmTrace.CodeHash = &codeHash
} else if tx.To() == nil { // Contract is created.
evmTrace.ByteCode = hexutil.Encode(tx.Data())
}
}
if err := zkproof.FillBlockResultForMPTWitness(zkproof.MPTWitnessType(bc.cacheConfig.MPTWitness), blockResult); err != nil {
log.Error("fill mpt witness fail", "error", err)
}
return blockResult
}
// addFutureBlock checks if the block is within the max allowed window to get
// accepted for future processing, and returns an error if the block is too far
// ahead and was not added.
@ -1706,7 +1655,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
// Write the block to the chain and get the status.
substart = time.Now()
// EvmTraces & StorageTrace being nil is safe because l2geth's p2p server is stoped and the code will not execute there.
status, err := bc.writeBlockWithState(block, receipts, logs, nil, nil, statedb, false)
status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false)
atomic.StoreUint32(&followupInterrupt, 1)
if err != nil {
return it.index, err

View file

@ -158,13 +158,6 @@ func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
return bc.GetBlock(hash, *number)
}
func (bc *BlockChain) GetBlockResultByHash(blockHash common.Hash) *types.BlockResult {
if blockResult, ok := bc.blockResultCache.Get(blockHash); ok {
return blockResult.(*types.BlockResult)
}
return nil
}
// GetBlockByNumber retrieves a block from the database by number, caching it
// (associated with its hash) if found.
func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
@ -312,6 +305,9 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
// Config retrieves the chain's fork configuration.
func (bc *BlockChain) Config() *params.ChainConfig { return bc.chainConfig }
// CacheConfig retrieves the chain's cacheConfig.
func (bc *BlockChain) CacheConfig() *CacheConfig { return bc.cacheConfig }
// Engine retrieves the blockchain's consensus engine.
func (bc *BlockChain) Engine() consensus.Engine { return bc.engine }

View file

@ -34,7 +34,6 @@ type ChainEvent struct {
Block *types.Block
Hash common.Hash
Logs []*types.Log
BlockResult *types.BlockResult
}
type ChainSideEvent struct {

View file

@ -49,7 +49,7 @@ type StorageTrace struct {
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue,omitempty"`
ReturnValue string `json:"returnValue"`
// Sender's account state (before Tx)
From *AccountWrapper `json:"from,omitempty"`
// Receiver's account state (before Tx)

View file

@ -607,21 +607,3 @@ func (api *PrivateDebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64
}
return 0, fmt.Errorf("No state found")
}
// PublicTraceAPI provides an API to get evmTrace, mpt proof.
type PublicTraceAPI struct {
e *Ethereum
}
// NewPublicTraceAPI creates a new Ethereum trace API.
func NewPublicTraceAPI(eth *Ethereum) *PublicTraceAPI {
return &PublicTraceAPI{eth}
}
// GetBlockResultByHash returns the blockResult by blockHash.
func (api *PublicTraceAPI) GetBlockResultByHash(blockHash common.Hash) (*types.BlockResult, error) {
if blockResult := api.e.blockchain.GetBlockResultByHash(blockHash); blockResult != nil {
return blockResult, nil
}
return nil, fmt.Errorf("No block result found")
}

View file

@ -53,6 +53,10 @@ func (b *EthAPIBackend) ChainConfig() *params.ChainConfig {
return b.eth.blockchain.Config()
}
func (b *EthAPIBackend) CacheConfig() *core.CacheConfig {
return b.eth.blockchain.CacheConfig()
}
func (b *EthAPIBackend) CurrentBlock() *types.Block {
return b.eth.blockchain.CurrentBlock()
}

View file

@ -175,8 +175,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
var (
vmConfig = vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording,
Debug: true,
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: false}),
}
cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache,
@ -188,7 +186,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
TrieTimeLimit: config.TrieTimeout,
SnapshotLimit: config.SnapshotCache,
Preimages: config.Preimages,
TraceCacheLimit: config.TraceCacheLimit,
MPTWitness: config.MPTWitness,
}
)
@ -318,11 +315,6 @@ func (s *Ethereum) APIs() []rpc.API {
Version: "1.0",
Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
Public: true,
}, {
Namespace: "eth",
Version: "1.0",
Service: NewPublicTraceAPI(s),
Public: true,
}, {
Namespace: "miner",
Version: "1.0",

View file

@ -206,7 +206,6 @@ type Config struct {
OverrideArrowGlacier *big.Int `toml:",omitempty"`
// Trace option
TraceCacheLimit int
MPTWitness int
}

View file

@ -278,35 +278,6 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc
return rpcSub, nil
}
// NewBlockResult sends the block execution result when a new block is created.
func (api *PublicFilterAPI) NewBlockResult(ctx context.Context) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
}
rpcSub := notifier.CreateSubscription()
go func() {
blockResults := make(chan *types.BlockResult)
blockResultsSub := api.events.SubscribeBlockResult(blockResults)
for {
select {
case blockResult := <-blockResults:
notifier.Notify(rpcSub.ID, blockResult)
case <-rpcSub.Err():
blockResultsSub.Unsubscribe()
return
case <-notifier.Closed():
blockResultsSub.Unsubscribe()
return
}
}
}()
return rpcSub, nil
}
// FilterCriteria represents a request to create a new filter.
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
type FilterCriteria ethereum.FilterQuery

View file

@ -52,8 +52,6 @@ const (
PendingTransactionsSubscription
// BlocksSubscription queries hashes for blocks that are imported
BlocksSubscription
// BlockResultsSubscription queries for block execution traces
BlockResultsSubscription
// LastSubscription keeps track of the last index
LastIndexSubscription
)
@ -78,7 +76,6 @@ type subscription struct {
logs chan []*types.Log
hashes chan []common.Hash
headers chan *types.Header
blockResults chan *types.BlockResult
installed chan struct{} // closed when the filter is installed
err chan error // closed when the filter is uninstalled
}
@ -293,19 +290,6 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
return es.subscribe(sub)
}
// SubscribeBlockResult creates a subscription that writes the block trace when a new block is created.
func (es *EventSystem) SubscribeBlockResult(blockResult chan *types.BlockResult) *Subscription {
sub := &subscription{
id: rpc.NewID(),
typ: BlockResultsSubscription,
created: time.Now(),
blockResults: blockResult,
installed: make(chan struct{}),
err: make(chan error),
}
return es.subscribe(sub)
}
// SubscribePendingTxs creates a subscription that writes transaction hashes for
// transactions that enter the transaction pool.
func (es *EventSystem) SubscribePendingTxs(hashes chan []common.Hash) *Subscription {
@ -371,11 +355,6 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent)
for _, f := range filters[BlocksSubscription] {
f.headers <- ev.Block.Header()
}
if ev.BlockResult != nil {
for _, f := range filters[BlockResultsSubscription] {
f.blockResults <- ev.BlockResult
}
}
if es.lightMode && len(filters[LogsSubscription]) > 0 {
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) {
for _, f := range filters[LogsSubscription] {

View file

@ -72,6 +72,7 @@ type Backend interface {
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
RPCGasCap() uint64
ChainConfig() *params.ChainConfig
CacheConfig() *core.CacheConfig
Engine() consensus.Engine
ChainDb() ethdb.Database
// StateAtBlock returns the state corresponding to the stateroot of the block.
@ -937,5 +938,11 @@ func APIs(backend Backend) []rpc.API {
Service: NewAPI(backend),
Public: false,
},
{
Namespace: "scroll",
Version: "1.0",
Service: TraceBlock(NewAPI(backend)),
Public: true,
},
}
}

View file

@ -0,0 +1,364 @@
package tracers
import (
"context"
"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/core"
"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/rpc"
"github.com/scroll-tech/go-ethereum/trie/zkproof"
)
type TraceBlock interface {
GetBlockResultByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (trace *types.BlockResult, err error)
}
type traceEnv struct {
config *TraceConfig
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
executionResults []*types.ExecutionResult
}
// GetBlockResultByNumberOrHash replays the block and returns the structured BlockResult by hash or number.
func (api *API) GetBlockResultByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (trace *types.BlockResult, err error) {
var block *types.Block
if number, ok := blockNrOrHash.Number(); ok {
block, err = api.blockByNumber(ctx, number)
}
if hash, ok := blockNrOrHash.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
}
if err != nil {
return nil, err
}
if block.NumberU64() == 0 {
return nil, errors.New("genesis is not traceable")
}
if config == nil {
config = &TraceConfig{
LogConfig: &vm.LogConfig{
EnableMemory: false,
EnableReturnData: true,
},
}
} else if config.Tracer != nil {
config.Tracer = nil
log.Warn("Tracer params is unsupported")
}
// create current execution environment.
env, err := api.createTraceEnv(ctx, config, block)
if err != nil {
return nil, err
}
return api.getBlockResult(block, env)
}
// Make trace environment for current block.
func (api *API) createTraceEnv(ctx context.Context, config *TraceConfig, block *types.Block) (*traceEnv, error) {
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, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, true)
if err != nil {
return nil, err
}
// get coinbase
coinbase, err := api.backend.Engine().Author(block.Header())
if err != nil {
return nil, err
}
env := &traceEnv{
config: config,
coinbase: coinbase,
signer: types.MakeSigner(api.backend.ChainConfig(), block.Number()),
state: statedb,
blockCtx: core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil),
StorageTrace: &types.StorageTrace{
RootBefore: parent.Root(),
RootAfter: block.Root(),
Proofs: make(map[string][]hexutil.Bytes),
StorageProofs: make(map[string]map[string][]hexutil.Bytes),
},
executionResults: make([]*types.ExecutionResult, 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 (api *API) getBlockResult(block *types.Block, env *traceEnv) (*types.BlockResult, 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 := api.getTxResult(env, 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, core.NewEVMTxContext(msg), env.state, api.backend.ChainConfig(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
failed = err
break
}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
env.state.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
}
close(jobs)
pend.Wait()
// If execution failed in between, abort
select {
case err := <-errCh:
return nil, err
default:
if failed != nil {
return nil, failed
}
}
return api.fillBlockResult(env, block)
}
func (api *API) getTxResult(env *traceEnv, 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)),
CodeHash: state.GetCodeHash(from),
}
var receiver *types.AccountWrapper
if to != nil {
receiver = &types.AccountWrapper{
Address: *to,
Nonce: state.GetNonce(*to),
Balance: (*hexutil.Big)(state.GetBalance(*to)),
CodeHash: state.GetCodeHash(*to),
}
}
tracer := vm.NewStructLogger(env.config.LogConfig)
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), state, api.backend.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.
result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
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)),
CodeHash: state.GetCodeHash(acc),
})
}
// merge required proof data
proofAccounts := tracer.UpdatedAccounts()
for addr := range proofAccounts {
addrStr := addr.String()
env.pMu.Lock()
_, existed := env.Proofs[addrStr]
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
env.pMu.Unlock()
}
proofStorages := tracer.UpdatedStorages()
for addr, keys := range proofStorages {
for key := range keys {
addrStr := addr.String()
keyStr := key.String()
env.sMu.Lock()
m, existed := env.StorageProofs[addrStr]
if !existed {
m = make(map[string][]hexutil.Bytes)
env.StorageProofs[addrStr] = m
} else if _, existed := m[keyStr]; existed {
env.sMu.Unlock()
continue
}
env.sMu.Unlock()
proof, err := state.GetStorageTrieProof(addr, 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()
m[keyStr] = wrappedProof
env.sMu.Unlock()
}
}
env.executionResults[index] = &types.ExecutionResult{
From: sender,
To: receiver,
AccountCreated: createdAcc,
AccountsAfter: after,
Gas: result.UsedGas,
Failed: result.Failed(),
ReturnValue: fmt.Sprintf("%x", returnVal),
StructLogs: vm.FormatLogs(tracer.StructLogs()),
}
return nil
}
// Fill blockResult content after all the txs are finished running.
func (api *API) fillBlockResult(env *traceEnv, block *types.Block) (*types.BlockResult, error) {
statedb := env.state
txs := block.Transactions()
coinbase := types.AccountWrapper{
Address: env.coinbase,
Nonce: statedb.GetNonce(env.coinbase),
Balance: (*hexutil.Big)(statedb.GetBalance(env.coinbase)),
CodeHash: statedb.GetCodeHash(env.coinbase),
}
blockResult := &types.BlockResult{
BlockTrace: types.NewTraceBlock(api.backend.ChainConfig(), block, &coinbase),
StorageTrace: env.StorageTrace,
ExecutionResults: env.executionResults,
}
for i, tx := range txs {
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.GetCodeHash(*tx.To())
evmTrace.CodeHash = &codeHash
} else if tx.To() == nil { // Contract is created.
evmTrace.ByteCode = hexutil.Encode(tx.Data())
}
}
if err := zkproof.FillBlockResultForMPTWitness(zkproof.MPTWitnessType(api.backend.CacheConfig().MPTWitness), blockResult); err != nil {
log.Error("fill mpt witness fail", "error", err)
}
return blockResult, nil
}

File diff suppressed because one or more lines are too long

View file

@ -130,6 +130,10 @@ func (b *testBackend) ChainConfig() *params.ChainConfig {
return b.chainConfig
}
func (b *testBackend) CacheConfig() *core.CacheConfig {
return b.chain.CacheConfig()
}
func (b *testBackend) Engine() consensus.Engine {
return b.engine
}

View file

@ -325,13 +325,16 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
return ec.c.EthSubscribe(ctx, ch, "newHeads")
}
// GetBlockResultByHash returns the blockResult.
// GetBlockResultByHash returns the BlockResult given the block hash.
func (ec *Client) GetBlockResultByHash(ctx context.Context, blockHash common.Hash) (*types.BlockResult, error) {
var blockResult types.BlockResult
if err := ec.c.CallContext(ctx, &blockResult, "eth_getBlockResultByHash", blockHash); err != nil {
return nil, err
}
return &blockResult, nil
blockResult := &types.BlockResult{}
return blockResult, ec.c.CallContext(ctx, &blockResult, "scroll_getBlockResultByNumberOrHash", blockHash)
}
// GetBlockResultByNumber returns the BlockResult given the block number.
func (ec *Client) GetBlockResultByNumber(ctx context.Context, number *big.Int) (*types.BlockResult, error) {
blockResult := &types.BlockResult{}
return blockResult, ec.c.CallContext(ctx, &blockResult, "scroll_getBlockResultByNumberOrHash", toBlockNumArg(number))
}
// SubscribeNewBlockResult subscribes to block execution trace when a new block is created.

View file

@ -91,12 +91,6 @@ var (
Name: "trace",
Usage: "Write execution trace to the given file",
}
// NoTrace settings
traceCacheLimitFlag = cli.IntFlag{
Name: "trace.limit",
Usage: "Handle the latest several blockResults",
Value: 32,
}
// mpt witness settings
mptWitnessFlag = cli.IntFlag{
Name: "trace.mptwitness",
@ -119,7 +113,6 @@ var Flags = []cli.Flag{
blockprofilerateFlag,
cpuprofileFlag,
traceFlag,
traceCacheLimitFlag,
mptWitnessFlag,
}
@ -135,14 +128,12 @@ func init() {
type TraceConfig struct {
TracePath string
// Trace option
TraceCacheLimit int
MPTWitness int
}
func ConfigTrace(ctx *cli.Context) *TraceConfig {
cfg := new(TraceConfig)
cfg.TracePath = ctx.GlobalString(traceFlag.Name)
cfg.TraceCacheLimit = ctx.GlobalInt(traceCacheLimitFlag.Name)
cfg.MPTWitness = ctx.GlobalInt(mptWitnessFlag.Name)
return cfg

View file

@ -51,6 +51,10 @@ func (b *LesApiBackend) ChainConfig() *params.ChainConfig {
return b.eth.chainConfig
}
func (b *LesApiBackend) CacheConfig() *core.CacheConfig {
return nil
}
func (b *LesApiBackend) CurrentBlock() *types.Block {
return types.NewBlockWithHeader(b.eth.BlockChain().CurrentHeader())
}

View file

@ -19,7 +19,6 @@ package miner
import (
"bytes"
"errors"
"fmt"
"math/big"
"sync"
"sync/atomic"
@ -28,13 +27,11 @@ import (
mapset "github.com/deckarep/golang-set"
"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/consensus/misc"
"github.com/scroll-tech/go-ethereum/core"
"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/event"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params"
@ -95,16 +92,11 @@ type environment struct {
header *types.Header
txs []*types.Transaction
receipts []*types.Receipt
executionResults []*types.ExecutionResult
proofs map[string][]hexutil.Bytes
storageProofs map[string]map[string][]hexutil.Bytes
}
// task contains all information for consensus engine sealing and result submitting.
type task struct {
receipts []*types.Receipt
executionResults []*types.ExecutionResult
storageResults *types.StorageTrace
state *state.StateDB
block *types.Block
createdAt time.Time
@ -643,20 +635,13 @@ func (w *worker) resultLoop() {
// Different block could share same sealhash, deep copy here to prevent write-write conflict.
var (
receipts = make([]*types.Receipt, len(task.receipts))
evmTraces = make([]*types.ExecutionResult, len(task.executionResults))
logs []*types.Log
storageTrace = new(types.StorageTrace)
)
for i, taskReceipt := range task.receipts {
receipt := new(types.Receipt)
receipts[i] = receipt
*receipt = *taskReceipt
evmTrace := new(types.ExecutionResult)
evmTraces[i] = evmTrace
*evmTrace = *task.executionResults[i]
*storageTrace = *task.storageResults
// add block location fields
receipt.BlockHash = hash
receipt.BlockNumber = block.Number()
@ -674,7 +659,7 @@ func (w *worker) resultLoop() {
logs = append(logs, receipt.Logs...)
}
// Commit block and state to database.
_, err := w.chain.WriteBlockWithState(block, receipts, logs, evmTraces, storageTrace, task.state, true)
_, err := w.chain.WriteBlockWithState(block, receipts, logs, task.state, true)
if err != nil {
log.Error("Failed writing block to chain", "err", err)
continue
@ -704,28 +689,6 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
}
state.StartPrefetcher("miner")
proofs := make(map[string][]hexutil.Bytes)
// init proof with coinbase's proof
for _, coinbase := range []common.Address{w.coinbase, header.Coinbase} {
if coinbase == (common.Address{}) {
continue
}
key := coinbase.String()
if _, exist := proofs[key]; !exist {
proof, err := 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
}
proofs[key] = wrappedProof
}
}
env := &environment{
signer: types.MakeSigner(w.chainConfig, header.Number),
state: state,
@ -733,8 +696,6 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
family: mapset.NewSet(),
uncles: mapset.NewSet(),
header: header,
proofs: proofs,
storageProofs: make(map[string]map[string][]hexutil.Bytes),
}
// when 08 is processed ancestors contain 07 (quick block)
for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) {
@ -812,114 +773,14 @@ func (w *worker) updateSnapshot() {
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
snap := w.current.state.Snapshot()
// reset tracer.
tracer := w.chain.GetVMConfig().Tracer.(*vm.StructLogger)
tracer.Reset()
// Get sender's address.
from, _ := types.Sender(w.current.signer, tx)
sender := &types.AccountWrapper{
Address: from,
Nonce: w.current.state.GetNonce(from),
Balance: (*hexutil.Big)(w.current.state.GetBalance(from)),
CodeHash: w.current.state.GetCodeHash(from),
}
// Get receiver's address.
var receiver *types.AccountWrapper
if tx.To() != nil {
to := *tx.To()
receiver = &types.AccountWrapper{
Address: to,
Nonce: w.current.state.GetNonce(to),
Balance: (*hexutil.Big)(w.current.state.GetBalance(to)),
CodeHash: w.current.state.GetCodeHash(to),
}
}
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig())
if err != nil {
w.current.state.RevertToSnapshot(snap)
return nil, err
}
createdAcc := tracer.CreatedAccount()
var after []*types.AccountWrapper
to := tx.To()
if to == nil {
if createdAcc == nil {
panic("unexpected tx: address for created contract unavialable")
}
to = &createdAcc.Address
}
// collect affected account after tx being applied
for acc := range map[common.Address]bool{from: true, (*to): true, w.coinbase: true} {
after = append(after, &types.AccountWrapper{
Address: acc,
Nonce: w.current.state.GetNonce(acc),
Balance: (*hexutil.Big)(w.current.state.GetBalance(acc)),
CodeHash: w.current.state.GetCodeHash(acc),
})
}
// merge required proof data
proofAccounts := tracer.UpdatedAccounts()
for addr := range proofAccounts {
addrStr := addr.String()
if _, existed := w.current.proofs[addrStr]; !existed {
proof, err := w.current.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
}
w.current.proofs[addrStr] = wrappedProof
}
}
proofStorages := tracer.UpdatedStorages()
for addr, keys := range proofStorages {
for key := range keys {
addrStr := addr.String()
m, existed := w.current.storageProofs[addrStr]
if !existed {
m = make(map[string][]hexutil.Bytes)
w.current.storageProofs[addrStr] = m
}
keyStr := key.String()
if _, existed := m[keyStr]; !existed {
proof, err := w.current.state.GetStorageTrieProof(addr, 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
}
m[keyStr] = wrappedProof
}
}
}
w.current.txs = append(w.current.txs, tx)
w.current.receipts = append(w.current.receipts, receipt)
w.current.executionResults = append(w.current.executionResults, &types.ExecutionResult{
Gas: receipt.GasUsed,
From: sender,
To: receiver,
AccountCreated: createdAcc,
AccountsAfter: after,
Failed: receipt.Status != types.ReceiptStatusSuccessful,
ReturnValue: fmt.Sprintf("%x", receipt.ReturnValue),
StructLogs: vm.FormatLogs(tracer.StructLogs()),
})
return receipt.Logs, nil
}
@ -1174,26 +1035,16 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
// Deep copy receipts here to avoid interaction between different tasks.
receipts := copyReceipts(w.current.receipts)
s := w.current.state.Copy()
// when block is not mined by myself, we just omit
// complete storage before Finalize state (only RootAfter left unknown)
storage := &types.StorageTrace{
RootBefore: s.GetRootHash(),
Proofs: w.current.proofs,
StorageProofs: w.current.storageProofs,
}
block, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, w.current.txs, uncles, receipts)
if err != nil {
return err
}
storage.RootAfter = block.Header().Root
if w.isRunning() {
if interval != nil {
interval()
}
select {
case w.taskCh <- &task{receipts: receipts, executionResults: w.current.executionResults, storageResults: storage, state: s, block: block, createdAt: time.Now()}:
case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
w.unconfirmed.Shift(block.NumberU64() - 1)
log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
"uncles", len(uncles), "txs", w.current.tcount,