core/state: fix account prefetching for absent accounts (#35256)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

This commit is contained in:
Ignacio Hagopian 2026-06-29 22:03:12 -03:00 committed by GitHub
parent 68671a4530
commit 04bf045303
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 5 deletions

View file

@ -614,16 +614,17 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
}
s.AccountReads += time.Since(start)
// Short circuit if the account is not found
if acct == nil {
return nil
}
// Schedule the resolved account for prefetching if it's enabled.
// Schedule the account path for prefetching if it's enabled. Even if the
// account is absent, the trie path proves its non-existence for witnesses.
if s.prefetcher != nil {
if err = s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, []common.Address{addr}, nil, true); err != nil {
log.Error("Failed to prefetch account", "addr", addr, "err", err)
}
}
// Short circuit if the account is not found
if acct == nil {
return nil
}
// Insert into the live set
obj := newObject(s, addr, acct)
s.setStateObject(obj)

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@ -998,6 +999,45 @@ func TestDeleteCreateRevert(t *testing.T) {
}
}
func TestWitnessIncludesAbsentAccountReads(t *testing.T) {
db := NewDatabaseForTesting()
state, _ := New(types.EmptyRootHash, db)
for i := byte(0); i < 3; i++ {
addr := common.Address{i + 1}
state.SetBalance(addr, uint256.NewInt(uint64(i+1)), tracing.BalanceChangeUnspecified)
}
root, err := state.Commit(0, false, false)
if err != nil {
t.Fatalf("failed to commit initial state: %v", err)
}
state, err = New(root, db)
if err != nil {
t.Fatalf("failed to reopen state: %v", err)
}
witness := &stateless.Witness{
Codes: make(map[string]struct{}),
State: make(map[string]struct{}),
}
state.StartPrefetcher("test", witness)
missing := common.HexToAddress("0x017655eac00c837122cabbbc0dd604a196906648")
if balance := state.GetBalance(missing); balance.Sign() != 0 {
t.Fatalf("unexpected balance for absent account: %v", balance)
}
if err := state.Error(); err != nil {
t.Fatalf("unexpected state error after read: %v", err)
}
if got := state.IntermediateRoot(false); got != root {
t.Fatalf("unexpected root after read-only access: have %x want %x", got, root)
}
if err := state.Error(); err != nil {
t.Fatalf("unexpected state error after root calculation: %v", err)
}
if len(witness.State) == 0 {
t.Fatal("missing witness nodes for absent account read")
}
}
// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie,
// the Commit operation fails with an error
// If we are missing trie nodes, we should not continue writing to the trie