mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
367 lines
11 KiB
Go
367 lines
11 KiB
Go
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())
|
|
}
|
|
}
|
|
|
|
// only zktrie model has the ability to get `mptwitness`.
|
|
if api.backend.ChainConfig().Zktrie {
|
|
if err := zkproof.FillBlockResultForMPTWitness(zkproof.MPTWitnessType(api.backend.CacheConfig().MPTWitness), blockResult); err != nil {
|
|
log.Error("fill mpt witness fail", "error", err)
|
|
}
|
|
}
|
|
|
|
return blockResult, nil
|
|
}
|