mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 00:39:26 +00:00
fix crash when attempting to call debug_getAccessList on block that doesn't contain one. remove the new pre/post-tx tracer hooks and use the system contract start/end hooks instead in the bal tracer
This commit is contained in:
parent
abaef48d54
commit
630a79e9d4
6 changed files with 80 additions and 16 deletions
|
|
@ -17,6 +17,18 @@ type BlockAccessListTracer struct {
|
||||||
|
|
||||||
// the access list index that changes are currently being recorded into
|
// the access list index that changes are currently being recorded into
|
||||||
balIdx uint16
|
balIdx uint16
|
||||||
|
|
||||||
|
// the number of system calls that have been invoked, used when building
|
||||||
|
// an access list to determine if the system calls being executed are
|
||||||
|
// before/after the block transactions.
|
||||||
|
sysCallCount int
|
||||||
|
|
||||||
|
// true if the tracer is processing post-tx state changes. in this case
|
||||||
|
// we won't record the final index after the end of the second post-tx
|
||||||
|
// system contract but after the finalization of the block.
|
||||||
|
// This is because we have EIP-4895 withdrawals which are processed after the
|
||||||
|
// last system contracts execute and must be included in the BAL.
|
||||||
|
isPostTx bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockAccessListTracer returns an BlockAccessListTracer and a set of hooks
|
// NewBlockAccessListTracer returns an BlockAccessListTracer and a set of hooks
|
||||||
|
|
@ -26,7 +38,6 @@ func NewBlockAccessListTracer() (*BlockAccessListTracer, *tracing.Hooks) {
|
||||||
}
|
}
|
||||||
hooks := &tracing.Hooks{
|
hooks := &tracing.Hooks{
|
||||||
OnBlockFinalization: balTracer.OnBlockFinalization,
|
OnBlockFinalization: balTracer.OnBlockFinalization,
|
||||||
OnPreTxExecutionDone: balTracer.OnPreTxExecutionDone,
|
|
||||||
OnTxEnd: balTracer.TxEndHook,
|
OnTxEnd: balTracer.TxEndHook,
|
||||||
OnTxStart: balTracer.TxStartHook,
|
OnTxStart: balTracer.TxStartHook,
|
||||||
OnEnter: balTracer.OnEnter,
|
OnEnter: balTracer.OnEnter,
|
||||||
|
|
@ -38,11 +49,16 @@ func NewBlockAccessListTracer() (*BlockAccessListTracer, *tracing.Hooks) {
|
||||||
OnStorageRead: balTracer.OnStorageRead,
|
OnStorageRead: balTracer.OnStorageRead,
|
||||||
OnAccountRead: balTracer.OnAcountRead,
|
OnAccountRead: balTracer.OnAcountRead,
|
||||||
OnSelfDestructChange: balTracer.OnSelfDestruct,
|
OnSelfDestructChange: balTracer.OnSelfDestruct,
|
||||||
|
OnSystemCallEnd: balTracer.OnSystemCallEnd,
|
||||||
}
|
}
|
||||||
wrappedHooks, _ := tracing.WrapWithJournal(hooks)
|
wrappedHooks, _ := tracing.WrapWithJournal(hooks)
|
||||||
return balTracer, wrappedHooks
|
return balTracer, wrappedHooks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *BlockAccessListTracer) SetPostTx() {
|
||||||
|
a.isPostTx = true
|
||||||
|
}
|
||||||
|
|
||||||
// AccessList returns the constructed access list.
|
// AccessList returns the constructed access list.
|
||||||
// It is assumed that this is only called after all the block state changes
|
// It is assumed that this is only called after all the block state changes
|
||||||
// have been executed and the block has been finalized.
|
// have been executed and the block has been finalized.
|
||||||
|
|
@ -50,9 +66,15 @@ func (a *BlockAccessListTracer) AccessList() *bal.AccessListBuilder {
|
||||||
return a.builder
|
return a.builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *BlockAccessListTracer) OnPreTxExecutionDone() {
|
func (a *BlockAccessListTracer) OnSystemCallEnd() {
|
||||||
a.builder.FinaliseIdxChanges(0)
|
if a.isPostTx {
|
||||||
a.balIdx++
|
return
|
||||||
|
}
|
||||||
|
a.sysCallCount++
|
||||||
|
if a.sysCallCount == 2 {
|
||||||
|
a.builder.FinaliseIdxChanges(a.balIdx)
|
||||||
|
a.balIdx++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *BlockAccessListTracer) TxStartHook(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
|
func (a *BlockAccessListTracer) TxStartHook(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
|
||||||
|
|
|
||||||
|
|
@ -2231,7 +2231,6 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
defer func() {
|
defer func() {
|
||||||
bc.cfg.VmConfig.Tracer = nil
|
bc.cfg.VmConfig.Tracer = nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
// Process block using the parent state as reference point
|
// Process block using the parent state as reference point
|
||||||
pstart := time.Now()
|
pstart := time.Now()
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,12 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateR
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
|
|
||||||
balTracer, hooks := NewBlockAccessListTracer()
|
balTracer, hooks := NewBlockAccessListTracer()
|
||||||
|
balTracer.SetPostTx()
|
||||||
|
|
||||||
tracingStateDB := state.NewHookedState(postTxState, hooks)
|
tracingStateDB := state.NewHookedState(postTxState, hooks)
|
||||||
context := NewEVMBlockContext(header, p.chain, nil)
|
context := NewEVMBlockContext(header, p.chain, nil)
|
||||||
postTxState.SetAccessListIndex(len(block.Transactions()) + 1)
|
lastBALIdx := len(block.Transactions()) + 1
|
||||||
|
postTxState.SetAccessListIndex(lastBALIdx)
|
||||||
|
|
||||||
cfg := vm.Config{
|
cfg := vm.Config{
|
||||||
Tracer: hooks,
|
Tracer: hooks,
|
||||||
|
|
@ -122,6 +125,8 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateR
|
||||||
diff, stateReads := balTracer.builder.FinalizedIdxChanges()
|
diff, stateReads := balTracer.builder.FinalizedIdxChanges()
|
||||||
allStateReads.Merge(stateReads)
|
allStateReads.Merge(stateReads)
|
||||||
|
|
||||||
|
// TODO: if there is a failure, we need to print out the detailed logs explaining why the BAL validation failed
|
||||||
|
// but logs are disabled when we are running tests to prevent a ton of output
|
||||||
balIdx := len(block.Transactions()) + 1
|
balIdx := len(block.Transactions()) + 1
|
||||||
if !postTxState.BlockAccessList().ValidateStateDiff(balIdx, diff) {
|
if !postTxState.BlockAccessList().ValidateStateDiff(balIdx, diff) {
|
||||||
return &ProcessResultWithMetrics{
|
return &ProcessResultWithMetrics{
|
||||||
|
|
@ -129,9 +134,9 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := postTxState.BlockAccessList().ValidateStateReads(*allStateReads); err != nil {
|
if !postTxState.BlockAccessList().ValidateStateReads(lastBALIdx, *allStateReads) {
|
||||||
return &ProcessResultWithMetrics{
|
return &ProcessResultWithMetrics{
|
||||||
ProcessResult: &ProcessResult{Error: err},
|
ProcessResult: &ProcessResult{Error: fmt.Errorf("BAL validation failure")},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,8 +320,6 @@ func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *st
|
||||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||||
}
|
}
|
||||||
|
|
||||||
balTracer.OnPreTxExecutionDone()
|
|
||||||
|
|
||||||
diff, stateReads := balTracer.builder.FinalizedIdxChanges()
|
diff, stateReads := balTracer.builder.FinalizedIdxChanges()
|
||||||
if !statedb.BlockAccessList().ValidateStateDiff(0, diff) {
|
if !statedb.BlockAccessList().ValidateStateDiff(0, diff) {
|
||||||
return nil, fmt.Errorf("BAL validation failure")
|
return nil, fmt.Errorf("BAL validation failure")
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,42 @@ func (r *BALReader) ModifiedAccounts() (res []common.Address) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BALReader) ValidateStateReads(computedReads bal.StateAccesses) error {
|
func logReadsDiff(idx int, address common.Address, computedReads map[common.Hash]struct{}, expectedReads []*bal.EncodedStorage) {
|
||||||
|
expectedReadsMap := make(map[common.Hash]struct{})
|
||||||
|
for _, er := range expectedReads {
|
||||||
|
expectedReadsMap[er.ToHash()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
allReads := make(map[common.Hash]struct{})
|
||||||
|
|
||||||
|
for er := range expectedReadsMap {
|
||||||
|
allReads[er] = struct{}{}
|
||||||
|
}
|
||||||
|
for cr := range computedReads {
|
||||||
|
allReads[cr] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var missingExpected, missingComputed []common.Hash
|
||||||
|
|
||||||
|
for storage := range allReads {
|
||||||
|
_, hasComputed := computedReads[storage]
|
||||||
|
_, hasExpected := expectedReadsMap[storage]
|
||||||
|
if hasComputed && !hasExpected {
|
||||||
|
missingExpected = append(missingExpected, storage)
|
||||||
|
}
|
||||||
|
if !hasComputed && hasExpected {
|
||||||
|
missingComputed = append(missingComputed, storage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missingExpected) > 0 {
|
||||||
|
log.Error("read storage slots which were not reported in the BAL", "index", idx, "address", address, missingExpected)
|
||||||
|
}
|
||||||
|
if len(missingComputed) > 0 {
|
||||||
|
log.Error("did not read storage slots which were reported in the BAL", "index", idx, "address", address, missingComputed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BALReader) ValidateStateReads(idx int, computedReads bal.StateAccesses) bool {
|
||||||
// 1. remove any slots from 'allReads' which were written
|
// 1. remove any slots from 'allReads' which were written
|
||||||
// 2. validate that the read set in the BAL matches 'allReads' exactly
|
// 2. validate that the read set in the BAL matches 'allReads' exactly
|
||||||
for addr, reads := range computedReads {
|
for addr, reads := range computedReads {
|
||||||
|
|
@ -154,22 +189,25 @@ func (r *BALReader) ValidateStateReads(computedReads bal.StateAccesses) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := r.accesses[addr]; !ok {
|
if _, ok := r.accesses[addr]; !ok {
|
||||||
return fmt.Errorf("account %x was accessed during execution but is not present in the access list", addr)
|
log.Error(fmt.Sprintf("account %x was accessed during execution but is not present in the access list", addr))
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedReads := r.accesses[addr].StorageReads
|
expectedReads := r.accesses[addr].StorageReads
|
||||||
if len(reads) != len(expectedReads) {
|
if len(reads) != len(expectedReads) {
|
||||||
return fmt.Errorf("mismatch between the number of computed reads and number of expected reads")
|
logReadsDiff(idx, addr, reads, expectedReads)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, slot := range expectedReads {
|
for _, slot := range expectedReads {
|
||||||
if _, ok := reads[slot.ToHash()]; !ok {
|
if _, ok := reads[slot.ToHash()]; !ok {
|
||||||
return fmt.Errorf("expected read is missing from BAL")
|
log.Error("expected read is missing from BAL")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// changesAt returns all state changes occurring at the given index.
|
// changesAt returns all state changes occurring at the given index.
|
||||||
|
|
|
||||||
|
|
@ -546,5 +546,8 @@ func (api *DebugAPI) GetBlockAccessList(number rpc.BlockNumberOrHash) (interface
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return nil, fmt.Errorf("block not found")
|
return nil, fmt.Errorf("block not found")
|
||||||
}
|
}
|
||||||
|
if block.Body().AccessList == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return block.Body().AccessList.StringableRepresentation(), nil
|
return block.Body().AccessList.StringableRepresentation(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,6 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest, buildAndVerif
|
||||||
|
|
||||||
for _, snapshot := range snapshotConf {
|
for _, snapshot := range snapshotConf {
|
||||||
for _, dbscheme := range dbschemeConf {
|
for _, dbscheme := range dbschemeConf {
|
||||||
//tracer := logger.NewJSONLogger(&logger.Config{}, os.Stdout)
|
|
||||||
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, false, buildAndVerifyBAL, nil, nil)); err != nil {
|
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, false, buildAndVerifyBAL, nil, nil)); err != nil {
|
||||||
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
|
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue