go-ethereum/eth/tracers/api_blockresult.go
2022-09-26 10:32:47 +08:00

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
}