From 678b2ff2101c11bab3571a245090eec39676c755 Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Fri, 7 Aug 2026 12:48:05 +0700 Subject: [PATCH] execution/state: skip the domain read when the tx already found no account versionedStateReader.ReadAccountData consulted the domain whenever the read set held no account for the address, including when it held an AddressPath entry saying there is no account. readAccountInternal records that entry header-only before the load and accountRead overwrites it with the account as soon as a load finds one, so a header-only entry means the load came back empty. Measured over rpc/jsonrpc and execution/tests, the implication "read set records the address, the read returns nil" held 10618/10618. --- execution/state/versionedio.go | 7 ++-- execution/state/versionedio_test.go | 53 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/execution/state/versionedio.go b/execution/state/versionedio.go index 2080c2d03ef..36f1dedf938 100644 --- a/execution/state/versionedio.go +++ b/execution/state/versionedio.go @@ -1589,7 +1589,8 @@ func (vr *versionedStateReader) TracePrefix() string { } func (vr *versionedStateReader) ReadAccountData(address accounts.Address) (*accounts.Account, error) { - if r, ok := vr.reads.GetAddress(address); ok && r.Val != nil && !r.Val.IsNil() { + r, recorded := vr.reads.GetAddress(address) + if recorded && r.Val != nil && !r.Val.IsNil() { account := r.Val.Account() updated := vr.applyVersionedUpdates(address, *account) return &updated, nil @@ -1614,7 +1615,9 @@ func (vr *versionedStateReader) ReadAccountData(address accounts.Address) (*acco } } - if vr.stateReader != nil { + // A recorded AddressPath read with no account is the tx's own conclusion that + // the address holds nothing; the domain cannot say otherwise. + if vr.stateReader != nil && !recorded { account, err := vr.stateReader.ReadAccountData(address) if err != nil { diff --git a/execution/state/versionedio_test.go b/execution/state/versionedio_test.go index 45df39aa109..91c3a115cc8 100644 --- a/execution/state/versionedio_test.go +++ b/execution/state/versionedio_test.go @@ -17,6 +17,7 @@ package state import ( + "errors" "fmt" "sort" "testing" @@ -1332,6 +1333,58 @@ func TestVersionedUpdates_EstimateCellConsumed(t *testing.T) { require.Equal(t, newStorage, storageGot, "Estimate-cell storage must be consumed, not stale") } +// countingStateReader records how many account reads reached the domain. +type countingStateReader struct { + minimalStateReader + acc *accounts.Account + accountReads int + failOnAccounts bool +} + +func (r *countingStateReader) ReadAccountData(addr accounts.Address) (*accounts.Account, error) { + r.accountReads++ + if r.failOnAccounts { + return nil, errors.New("domain must not be consulted") + } + return r.acc, nil +} + +// A tx that read an address and found no account records the AddressPath entry +// header-only; that entry is the answer, not a gap to fill from the domain. +func TestVersionedStateReader_RecordedAbsentSkipsDomain(t *testing.T) { + t.Parallel() + + addr := accounts.InternAddress(common.HexToAddress("0xab5e17")) + reads := ReadSet{} + reads.SetAddress(addr, VersionedRead[AccountView]{ + ReadHeader: ReadHeader{Source: StorageRead, Version: UnknownVersion}, + }) + + reader := &countingStateReader{failOnAccounts: true} + vr := NewVersionedStateReader(3, reads, NewVersionMap(nil), reader) + + got, err := vr.ReadAccountData(addr) + require.NoError(t, err) + require.Nil(t, got) + require.Zero(t, reader.accountReads, "recorded-absent read must not reach the domain") +} + +func TestVersionedStateReader_UnrecordedAddressReadsDomain(t *testing.T) { + t.Parallel() + + addr := accounts.InternAddress(common.HexToAddress("0xc01d")) + domainAcc := accounts.NewAccount() + domainAcc.Nonce = 9 + reader := &countingStateReader{acc: &domainAcc} + vr := NewVersionedStateReader(3, ReadSet{}, NewVersionMap(nil), reader) + + got, err := vr.ReadAccountData(addr) + require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, uint64(9), got.Nonce) + require.Equal(t, 1, reader.accountReads) +} + // writeSetFixture builds a WriteSet covering every path, multiple addresses and // storage keys — the input for the iteration closed-loop tests. func writeSetFixture() (*WriteSet, []string) {