From 908f5db7455d5fc72fba82c4326d4874626e53ae Mon Sep 17 00:00:00 2001 From: sudeepdino008 Date: Tue, 11 Aug 2026 11:43:47 +0200 Subject: [PATCH] execution/state: drop the committed tier from BlockStateCache (#23140) BlockStateCache's committed tier (committedAccounts / committedStorage) was a lazily-populated read cache of the pre-block base, seeded as a side-effect of CachedReaderV3 reads. Within a block that base (sd.mem -> StateCache -> files) is frozen -- only the block-end Flush changes it -- so the committed tier just duplicated StateCache and interfered with its LRU retention. Remove it: current-tier misses now fall through to the frozen base, which StateCache already caches. The current tier (in-block writes) and the writeLog / Flush path are unchanged. Verified against a from-genesis batched stage_exec re-execution on mainnet through the 2016 EIP-161 empty-account sweep window (block 2676607 and past --- .../block_cache_committed_storage_test.go | 147 --------------- .../block_cache_multiblock_flush_test.go | 20 +- .../state/finalize_reader_blockcache_test.go | 6 +- execution/state/parallel_fixes_test.go | 30 --- execution/state/rw_v3.go | 173 ++++-------------- execution/state/rw_v3_allocs_test.go | 58 +----- execution/state/system_call_storage_test.go | 15 -- 7 files changed, 41 insertions(+), 408 deletions(-) delete mode 100644 execution/state/block_cache_committed_storage_test.go diff --git a/execution/state/block_cache_committed_storage_test.go b/execution/state/block_cache_committed_storage_test.go deleted file mode 100644 index 7b1c6cab176..00000000000 --- a/execution/state/block_cache_committed_storage_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2026 The Erigon Authors -// This file is part of Erigon. -// -// Erigon is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Erigon is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with Erigon. If not, see . - -package state - -import ( - "sync" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/erigontech/erigon/common" - "github.com/erigontech/erigon/execution/types/accounts" -) - -// BenchmarkCommittedStorage prices the committed-storage cache path that every -// block worker hits on SLOAD. Run with -cpu=1,8,16 to see behaviour under the -// contention the parallel executor actually produces. -func BenchmarkCommittedStorage(b *testing.B) { - const nAddrs, nKeys = 64, 64 - addrs := make([]accounts.Address, nAddrs) - keys := make([]accounts.StorageKey, nKeys) - for i := range addrs { - addrs[i] = accounts.InternAddress(common.Address{byte(i), byte(i >> 8)}) - } - for i := range keys { - keys[i] = accounts.InternKey(common.Hash{byte(i), byte(i >> 8)}) - } - val := []byte{1, 2, 3, 4} - - // Warm read: all keys pre-filled, workers only read (the SLOAD cache-hit hot path). - b.Run("read_warm", func(b *testing.B) { - c := NewBlockStateCache() - for _, a := range addrs { - for _, k := range keys { - c.PutCommittedStorage(a, k, val) - } - } - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - i := 0 - for pb.Next() { - c.GetCommittedStorage(addrs[i&(nAddrs-1)], keys[(i*7)&(nKeys-1)]) - i++ - } - }) - }) - - // Read-through fill: first touch misses then fills (the profiled PutCommittedStorage - // contention), subsequent touches hit. - b.Run("fill_read", func(b *testing.B) { - c := NewBlockStateCache() - b.ResetTimer() - b.RunParallel(func(pb *testing.PB) { - i := 0 - for pb.Next() { - a, k := addrs[i&(nAddrs-1)], keys[(i*7)&(nKeys-1)] - if _, ok := c.GetCommittedStorage(a, k); !ok { - c.PutCommittedStorage(a, k, val) - } - i++ - } - }) - }) -} - -// committedStorage is a write-once, immutable pre-block view backed by a -// lock-free sync.Map. These pin the value semantics the reader relies on — in -// particular a cached empty slot (ok=true, nil value) must stay distinct from -// an uncached miss (ok=false). -func TestBlockStateCache_CommittedStorage_Semantics(t *testing.T) { - t.Parallel() - - cache := NewBlockStateCache() - addr := accounts.InternAddress(common.HexToAddress("0xc0ffee")) - key := accounts.InternKey(common.Hash{0x22}) - - got, ok := cache.GetCommittedStorage(addr, key) - require.False(t, ok, "uncached slot must miss") - require.Nil(t, got) - - cache.PutCommittedStorage(addr, key, []byte{0x01, 0x02}) - got, ok = cache.GetCommittedStorage(addr, key) - require.True(t, ok) - require.Equal(t, []byte{0x01, 0x02}, got) - - emptyKey := accounts.InternKey(common.Hash{0x33}) - cache.PutCommittedStorage(addr, emptyKey, nil) - got, ok = cache.GetCommittedStorage(addr, emptyKey) - require.True(t, ok, "a cached empty slot must report ok=true, not a miss") - require.Nil(t, got) - - got, ok = cache.GetCurrentStorage(addr, key) - require.True(t, ok, "GetCurrentStorage must fall back to committed when unwritten") - require.Equal(t, []byte{0x01, 0x02}, got) -} - -// All worker goroutines of a block share one cache and read committed storage -// concurrently; the lock-free path must be race-free and never lose a value. -func TestBlockStateCache_CommittedStorage_ConcurrentAccess(t *testing.T) { - t.Parallel() - - const nAddrs, nKeys = 8, 32 - addrList := make([]accounts.Address, nAddrs) - keyList := make([]accounts.StorageKey, nKeys) - for i := range addrList { - addrList[i] = accounts.InternAddress(common.Address{byte(i + 1)}) - } - for i := range keyList { - keyList[i] = accounts.InternKey(common.Hash{byte(i + 1)}) - } - // write-once value, deterministic in (addr,key) so reads can be checked. - val := func(ai, ki int) []byte { return []byte{byte(ai + 1), byte(ki + 1)} } - - cache := NewBlockStateCache() - var wg sync.WaitGroup - for g := range 16 { - wg.Add(1) - go func(g int) { - defer wg.Done() - for i := range 2000 { - ai := (g + i) % nAddrs - ki := (g*7 + i) % nKeys - a, k := addrList[ai], keyList[ki] - cache.PutCommittedStorage(a, k, val(ai, ki)) - if got, ok := cache.GetCommittedStorage(a, k); ok { - require.Equal(t, val(ai, ki), got) - } - cache.GetCurrentStorage(a, k) - } - }(g) - } - wg.Wait() -} diff --git a/execution/state/block_cache_multiblock_flush_test.go b/execution/state/block_cache_multiblock_flush_test.go index dcd400577ed..14edfcba13a 100644 --- a/execution/state/block_cache_multiblock_flush_test.go +++ b/execution/state/block_cache_multiblock_flush_test.go @@ -68,15 +68,9 @@ func TestBlockStateCacheFlushClearsAcrossBlocks(t *testing.T) { // in exec3_parallel.go (one BlockStateCache per blockExecutor/batch). cache := NewBlockStateCache() - // Block 1: syscall reads slot first (populating committedStorage with - // the pre-batch value, empty here), then writes 0x01, then Flush. - // The read is what CachedReaderV3.ReadAccountStorage does on first - // access — that's the production path that seeds committedStorage. + // Block 1: syscall writes 0x01, then Flush. const block1TxNum uint64 = 100 domains.SetTxNum(block1TxNum) - // Simulate the first read — value is empty (pre-batch slot is zero). - // CachedReaderV3 caches this as committed[slot] = nil/empty. - cache.PutCommittedStorage(addr, slot, nil) cache.WriteStorage(addr, slot, []byte{0x01}, block1TxNum) require.NoError(t, cache.Flush(domains, tx)) @@ -85,11 +79,8 @@ func TestBlockStateCacheFlushClearsAcrossBlocks(t *testing.T) { require.True(t, bytes.Equal(enc1, []byte{0x01}), "after block 1 flush, domain should hold value 0x01, got %x", enc1) - // Block 2: syscall clears the slot back to 0x00. Prior to the fix the - // Flush dedup compared the (nil) cleared value against the stale - // `committedStorage[slot]` (still nil from a never-populated entry) - // and skipped the delete. After the fix the delete propagates and the - // domain no longer returns 0x01. + // Block 2: syscall clears the slot back to 0x00; the delete must + // propagate through Flush so the domain no longer returns 0x01. const block2TxNum uint64 = 200 domains.SetTxNum(block2TxNum) cache.WriteStorage(addr, slot, nil, block2TxNum) @@ -98,9 +89,7 @@ func TestBlockStateCacheFlushClearsAcrossBlocks(t *testing.T) { enc2, _, err := domains.GetLatest(kv.StorageDomain, tx, composite) require.NoError(t, err) require.Empty(t, enc2, - "after block 2 flush, domain should be cleared (value=empty); "+ - "got %x — this is the 24839762 trie-root race: Flush skipped "+ - "the delete because committedStorage was never refreshed", + "after block 2 flush, domain should be cleared (value=empty); got %x", ) } @@ -157,7 +146,6 @@ func TestBlockStateCacheFlushPreservesPerTxHistory(t *testing.T) { require.NoError(t, domains.DomainPut(kv.AccountsDomain, tx, addrVal[:], preEnc, preTxNum, nil)) cache := NewBlockStateCache() - cache.PutCommittedAccount(addr, &preAcc) // Tx 3 increments balance to 1100. tx3Acc := accounts.NewAccount() diff --git a/execution/state/finalize_reader_blockcache_test.go b/execution/state/finalize_reader_blockcache_test.go index 14a830da950..66502919057 100644 --- a/execution/state/finalize_reader_blockcache_test.go +++ b/execution/state/finalize_reader_blockcache_test.go @@ -84,9 +84,8 @@ func TestFinalizeReaderSeesBlockCacheWrite(t *testing.T) { domains.DomainPut(kv.AccountsDomain, tx, addrValue[:], preEnc, preBlockTxNum, nil), ) - // Simulate tx 28's SubBalance landing in the BlockStateCache (and only - // the BlockStateCache — applyVersionedWrites never touches sd.mem in the - // parallel path). + // Simulate tx 28's SubBalance landing in the BlockStateCache current tier + // (applyVersionedWrites buffers writes there until the block-end Flush). postTx28Balance := uint256.NewInt(6707) postAcc := &accounts.Account{ Nonce: 1, @@ -97,7 +96,6 @@ func TestFinalizeReaderSeesBlockCacheWrite(t *testing.T) { postEnc := accounts.SerialiseV3(postAcc) blockCache := NewBlockStateCache() - blockCache.PutCommittedAccount(addr, preAcc) blockCache.WriteAccount(addr, postEnc, 100) // Sanity: CurrentCachedReaderV3 (the reader used for non-historic diff --git a/execution/state/parallel_fixes_test.go b/execution/state/parallel_fixes_test.go index 02f7b26ef18..f468996b9c2 100644 --- a/execution/state/parallel_fixes_test.go +++ b/execution/state/parallel_fixes_test.go @@ -323,33 +323,6 @@ func TestTouchUpdates_MixedBatch(t *testing.T) { assert.Equal(t, uint64(2), updates.Size(), "2 unique keys (addr1 merged, addr2 storage)") } -// TestBlockStateCacheWriteAccount_NilCommitted verifies that WriteAccount -// doesn't panic when the committed cache has a nil account entry. -// This can happen when PutCommittedAccount stores nil (account doesn't exist). -func TestBlockStateCacheWriteAccount_NilCommitted(t *testing.T) { - cache := NewBlockStateCache() - - addr := accounts.InternAddress([20]byte{0x42}) - - // Put a nil committed account (account doesn't exist in pre-block state) - cache.PutCommittedAccount(addr, nil) - - // Write a new account — should not panic - acc := accounts.NewAccount() - acc.Balance = *uint256.NewInt(1000) - acc.Nonce = 1 - enc := accounts.SerialiseV3(&acc) - - assert.NotPanics(t, func() { - cache.WriteAccount(addr, enc, 1) - }, "WriteAccount should not panic with nil committed account") - - // Verify the write is recorded. - current, ok := cache.GetCurrentAccount(addr) - assert.True(t, ok, "Should have current account") - assert.Equal(t, enc, current, "Current account should match written value") -} - // TestBlockStateCacheWriteAccountUpdatesCurrent verifies that successive // writes update the current view to the latest value (last write wins // for read access via GetCurrentAccount). The full per-tx history is @@ -359,12 +332,9 @@ func TestBlockStateCacheWriteAccountUpdatesCurrent(t *testing.T) { addr := accounts.InternAddress([20]byte{0x55}) - // Set up committed account acc := accounts.NewAccount() acc.Balance = *uint256.NewInt(500) acc.Nonce = 3 - cache.PutCommittedAccount(addr, &acc) - enc := accounts.SerialiseV3(&acc) cache.WriteAccount(addr, enc, 3) diff --git a/execution/state/rw_v3.go b/execution/state/rw_v3.go index 1d68f14d7b7..6ed095ddaea 100644 --- a/execution/state/rw_v3.go +++ b/execution/state/rw_v3.go @@ -987,13 +987,6 @@ func (r *ReaderV3) TracePrefix() string { type BlockStateCache struct { mu sync.RWMutex - // committed holds pre-block state, lazily populated on first read. - // These values are returned by CachedReaderV3 for GetCommittedState. - // Both are write-once-per-key (an immutable pre-block view), so they are - // sync.Maps for lock-free reads off the shared mu hot path. - committedAccounts sync.Map // accounts.Address -> *accounts.Account (nil ptr = absent) - committedStorage sync.Map // committedStorageKey -> []byte (nil slice = cached empty slot) - // current holds the latest state including intra-block writes. // Updated by WriteAccount/WriteStorage. Read by block finalize. // At block boundary, dirty entries are flushed to SharedDomains. @@ -1021,12 +1014,6 @@ type BlockStateCache struct { writeLog []bcWriteOp } -// committedStorageKey is the sync.Map key for BlockStateCache.committedStorage. -type committedStorageKey struct { - addr accounts.Address - key accounts.StorageKey -} - // bcOpKind enumerates the operations recorded in BlockStateCache.writeLog. type bcOpKind uint8 @@ -1059,36 +1046,6 @@ func NewBlockStateCache() *BlockStateCache { } } -// --- Committed (read cache) methods --- - -// GetCommittedAccount returns the pre-block account, or (nil, false) if not cached. -func (c *BlockStateCache) GetCommittedAccount(addr accounts.Address) (*accounts.Account, bool) { - v, ok := c.committedAccounts.Load(addr) - if !ok { - return nil, false - } - return v.(*accounts.Account), true -} - -// PutCommittedAccount caches a pre-block account. Nil = doesn't exist. -func (c *BlockStateCache) PutCommittedAccount(addr accounts.Address, acc *accounts.Account) { - c.committedAccounts.Store(addr, acc) -} - -// GetCommittedStorage returns the pre-block storage value, or (nil, false) if not cached. -func (c *BlockStateCache) GetCommittedStorage(addr accounts.Address, key accounts.StorageKey) ([]byte, bool) { - v, ok := c.committedStorage.Load(committedStorageKey{addr: addr, key: key}) - if !ok { - return nil, false - } - return v.([]byte), true -} - -// PutCommittedStorage caches a pre-block storage value. nil = empty slot. -func (c *BlockStateCache) PutCommittedStorage(addr accounts.Address, key accounts.StorageKey, val []byte) { - c.committedStorage.Store(committedStorageKey{addr: addr, key: key}, val) -} - // --- Current (write buffer) methods --- // WriteAccount records an account write at txNum. The serialized blob @@ -1150,71 +1107,47 @@ func (c *BlockStateCache) DeleteAccount(addr accounts.Address, txNum uint64) { c.mu.Unlock() } -// GetCurrentAccountDecoded returns the latest account (including intra-block -// writes), avoiding GetCurrentAccount's re-encode of the committed entry. +// GetCurrentAccountDecoded returns the intra-block write for addr, if any. +// A miss (no in-block write) returns (nil, false, nil): the caller falls to the +// pre-block base (sd.mem → StateCache), which is frozen for the whole block. func (c *BlockStateCache) GetCurrentAccountDecoded(addr accounts.Address) (*accounts.Account, bool, error) { c.mu.RLock() enc, written := c.currentAccounts[addr] c.mu.RUnlock() - if written { - if enc == nil { - return nil, true, nil - } - acc := new(accounts.Account) - if err := accounts.DeserialiseV3(acc, enc); err != nil { - return nil, true, err - } - return acc, true, nil + if !written { + return nil, false, nil } - if acc, ok := c.GetCommittedAccount(addr); ok { - if acc == nil { - return nil, true, nil - } - result := *acc - return &result, true, nil + if enc == nil { + return nil, true, nil + } + acc := new(accounts.Account) + if err := accounts.DeserialiseV3(acc, enc); err != nil { + return nil, true, err } - return nil, false, nil + return acc, true, nil } -// GetCurrentAccount returns the latest account blob (including intra-block writes). -// Falls back to committed state if no write exists. Returns (nil, false) if not cached. +// GetCurrentAccount returns the intra-block account write for addr, if any. +// A miss returns (nil, false): the caller falls to the pre-block base. func (c *BlockStateCache) GetCurrentAccount(addr accounts.Address) ([]byte, bool) { c.mu.RLock() + defer c.mu.RUnlock() if enc, ok := c.currentAccounts[addr]; ok { - c.mu.RUnlock() return enc, true } - c.mu.RUnlock() - // The committed fallback runs after releasing mu, so the two reads are not one - // point-in-time snapshot. That is safe because committedAccounts is a - // write-once immutable pre-block view (a sync.Map for lock-free reads): a - // concurrent WriteAccount can only add a currentAccounts entry we'd miss — - // which the RLock-then-fallback ordering can't prevent regardless — never tear - // a committed value. - if v, ok := c.committedAccounts.Load(addr); ok { - acc := v.(*accounts.Account) - if acc == nil { - return nil, true - } - return accounts.SerialiseV3(acc), true - } return nil, false } -// GetCurrentStorage returns the latest storage value (including intra-block writes). -// Falls back to committed state if no write exists. Returns (nil, false) if not cached. +// GetCurrentStorage returns the intra-block storage write for addr/key, if any. +// A miss returns (nil, false): the caller falls to the pre-block base. func (c *BlockStateCache) GetCurrentStorage(addr accounts.Address, key accounts.StorageKey) ([]byte, bool) { c.mu.RLock() + defer c.mu.RUnlock() if slots, ok := c.currentStorage[addr]; ok { if val, ok := slots[key]; ok { - c.mu.RUnlock() return val, true } } - c.mu.RUnlock() - if v, ok := c.committedStorage.Load(committedStorageKey{addr: addr, key: key}); ok { - return v.([]byte), true - } return nil, false } @@ -1296,7 +1229,7 @@ func (c *BlockStateCache) Flush(domains *execctx.SharedDomains, roTx kv.Temporal type CachedReaderV3 struct { *ReaderV3 blockCache *BlockStateCache - readCurrent bool // when true, read from currentAccounts (post-TX) instead of committedAccounts (pre-block) + readCurrent bool // when true, read the current tier (post-TX writes) before the pre-block base } func NewCachedReaderV3(getter kv.TemporalGetter, blockCache *BlockStateCache) *CachedReaderV3 { @@ -1323,39 +1256,18 @@ func (r *CachedReaderV3) SetBlockStateCache(cache *BlockStateCache) { } func (r *CachedReaderV3) ReadAccountData(address accounts.Address) (*accounts.Account, error) { - if r.blockCache != nil { - if r.readCurrent { - // Sees accumulated per-TX writes. - acc, ok, err := r.blockCache.GetCurrentAccountDecoded(address) - if err != nil { - return nil, err - } - if ok { - return acc, nil - } - } else { - // Read from committed cache — stable pre-block view. - if acc, ok := r.blockCache.GetCommittedAccount(address); ok { - if acc == nil { - return nil, nil - } - result := *acc - return &result, nil - } + if r.blockCache != nil && r.readCurrent { + // Sees accumulated per-TX writes; a miss falls to the pre-block base below. + acc, ok, err := r.blockCache.GetCurrentAccountDecoded(address) + if err != nil { + return nil, err + } + if ok { + return acc, nil } } - acc, err := r.ReaderV3.ReadAccountData(address) - if err != nil { - return nil, err - } - if r.blockCache != nil { - r.blockCache.PutCommittedAccount(address, acc) - } - if acc != nil { - result := *acc - return &result, nil - } - return nil, nil + // Pre-block base (frozen for the whole block): sd.mem → StateCache → files. + return r.ReaderV3.ReadAccountData(address) } func (r *CachedReaderV3) ReadAccountCode(address accounts.Address) ([]byte, error) { @@ -1377,17 +1289,8 @@ func (r *CachedReaderV3) ReadAccountCodeSize(address accounts.Address) (int, err } func (r *CachedReaderV3) ReadAccountStorage(address accounts.Address, key accounts.StorageKey) (uint256.Int, bool, error) { - if r.blockCache != nil { - if r.readCurrent { - if val, ok := r.blockCache.GetCurrentStorage(address, key); ok { - var v uint256.Int - if len(val) > 0 { - v.SetBytes(val) - } - return v, len(val) > 0, nil - } - } - if val, ok := r.blockCache.GetCommittedStorage(address, key); ok { + if r.blockCache != nil && r.readCurrent { + if val, ok := r.blockCache.GetCurrentStorage(address, key); ok { var v uint256.Int if len(val) > 0 { v.SetBytes(val) @@ -1395,18 +1298,8 @@ func (r *CachedReaderV3) ReadAccountStorage(address accounts.Address, key accoun return v, len(val) > 0, nil } } - v, ok, err := r.ReaderV3.ReadAccountStorage(address, key) - if err != nil { - return v, ok, err - } - if r.blockCache != nil { - if ok { - r.blockCache.PutCommittedStorage(address, key, v.Bytes()) - } else { - r.blockCache.PutCommittedStorage(address, key, nil) - } - } - return v, ok, nil + // Pre-block base (frozen for the whole block): sd.mem → StateCache → files. + return r.ReaderV3.ReadAccountStorage(address, key) } func (r *ReaderV3) HasStorage(address accounts.Address) (bool, error) { diff --git a/execution/state/rw_v3_allocs_test.go b/execution/state/rw_v3_allocs_test.go index 27705a76244..0acca0d8ac1 100644 --- a/execution/state/rw_v3_allocs_test.go +++ b/execution/state/rw_v3_allocs_test.go @@ -57,11 +57,6 @@ func TestStateReader_ReadMethods_Allocs(t *testing.T) { key := accounts.InternKey(common.Hash{0x22}) hr := NewHistoryReaderV3(histMockTx{val: accEnc}, 0) - cache := NewBlockStateCache() - cache.PutCommittedStorage(addr, key, make([]byte, 32)) - cache.PutCommittedAccount(addr, &acc) - cr := NewCachedReaderV3(fixedGetter{val: make([]byte, 32)}, cache) - for _, tc := range []struct { name string want float64 @@ -81,11 +76,6 @@ func TestStateReader_ReadMethods_Allocs(t *testing.T) { {"HistoryReaderV3.ReadAccountData", 1, func() { _, _ = hr.ReadAccountData(addr) }}, // 1: returns *accounts.Account {"HistoryReaderV3.ReadAccountDataForDebug", 1, func() { _, _ = hr.ReadAccountDataForDebug(addr) }}, // 1: returns *accounts.Account - {"CachedReaderV3.ReadAccountStorage (cache hit)", 0, func() { _, _, _ = cr.ReadAccountStorage(addr, key) }}, - {"CachedReaderV3.ReadAccountData (cache hit)", 1, func() { _, _ = cr.ReadAccountData(addr) }}, // 1: returns *accounts.Account - {"CachedReaderV3.ReadAccountCode", 0, func() { _, _ = cr.ReadAccountCode(addr) }}, - {"CachedReaderV3.ReadAccountCodeSize", 0, func() { _, _ = cr.ReadAccountCodeSize(addr) }}, - {"CachedReaderV3.HasStorage", 0, func() { _, _ = cr.HasStorage(addr) }}, } { t.Run(tc.name, func(t *testing.T) { allocs := testing.AllocsPerRun(100, tc.fn) @@ -103,32 +93,13 @@ func cacheReadTestAccount() *accounts.Account { return &acc } -func TestCachedReaderV3_CurrentReadsCommittedWhenUnwritten(t *testing.T) { - t.Parallel() - - addr := accounts.InternAddress(common.HexToAddress("0xc0ffee")) - want := cacheReadTestAccount() - - cache := NewBlockStateCache() - cache.PutCommittedAccount(addr, want) - got, err := NewCurrentCachedReaderV3(nil, cache).ReadAccountData(addr) - require.NoError(t, err) - require.NotNil(t, got) - require.Equal(t, want.Nonce, got.Nonce) - require.Equal(t, want.Balance, got.Balance) - require.Equal(t, want.Incarnation, got.Incarnation) - require.Equal(t, want.CodeHash, got.CodeHash) - require.NotSame(t, want, got, "the caller must not be able to mutate the cached account") -} - -// A write this block shadows the committed view, and a nil write means the -// account was destroyed — neither may fall through to the committed entry. +// A block write is seen by the reader; a nil write means the account was +// destroyed this block and the reader returns nil. func TestCachedReaderV3_CurrentPrefersBlockWrite(t *testing.T) { t.Parallel() addr := accounts.InternAddress(common.HexToAddress("0xc0ffee")) cache := NewBlockStateCache() - cache.PutCommittedAccount(addr, cacheReadTestAccount()) written := cacheReadTestAccount() written.Nonce = 43 @@ -144,37 +115,12 @@ func TestCachedReaderV3_CurrentPrefersBlockWrite(t *testing.T) { require.Nil(t, got) } -func TestCachedReaderV3_CurrentReturnsNilForCommittedAbsence(t *testing.T) { - t.Parallel() - - addr := accounts.InternAddress(common.HexToAddress("0xdead")) - cache := NewBlockStateCache() - cache.PutCommittedAccount(addr, nil) - got, err := NewCurrentCachedReaderV3(nil, cache).ReadAccountData(addr) - require.NoError(t, err) - require.Nil(t, got) -} - // BenchmarkCachedReaderAccountRead prices one apply-loop account read that hits // the block state cache, on each of its two paths. func BenchmarkCachedReaderAccountRead(b *testing.B) { addr := accounts.InternAddress(common.HexToAddress("0xc0ffee")) acc := cacheReadTestAccount() - b.Run("committed", func(b *testing.B) { - cache := NewBlockStateCache() - cache.PutCommittedAccount(addr, acc) - r := NewCurrentCachedReaderV3(nil, cache) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - got, err := r.ReadAccountData(addr) - if err != nil || got == nil { - b.Fatal(err) - } - } - }) - b.Run("written", func(b *testing.B) { cache := NewBlockStateCache() cache.WriteAccount(addr, accounts.SerialiseV3(acc), 1) diff --git a/execution/state/system_call_storage_test.go b/execution/state/system_call_storage_test.go index e90e80a909b..da5f5ba9a47 100644 --- a/execution/state/system_call_storage_test.go +++ b/execution/state/system_call_storage_test.go @@ -105,17 +105,10 @@ func TestSystemCallStoragePropagation_BlockStateCache(t *testing.T) { // Read from cache (empty) → fallthrough to sd.mem val, ok := cache.GetCurrentStorage(addr, slot) - if !ok { - val, ok = cache.GetCommittedStorage(addr, slot) - } if !ok { // Fallthrough to sd.mem (simulates ReaderV3.ReadAccountStorage) val = sdMem[string(composite)] ok = len(val) > 0 - if ok { - // Populate committed cache (like CachedReaderV3 does) - cache.PutCommittedStorage(addr, slot, val) - } } t.Logf("Block %d: read slot4=%x (from %s)", blockIdx, val, func() string { @@ -172,8 +165,6 @@ func TestBlockStateCacheStorageWriteLog(t *testing.T) { addr := accounts.InternAddress([20]byte{0x42}) slot := accounts.InternKey([32]byte{0x01}) - cache.PutCommittedStorage(addr, slot, []byte{0x01}) - // Write same value — must still produce a writeLog entry so Flush // emits a DomainPut and the commitment touch is recorded. cache.WriteStorage(addr, slot, []byte{0x01}, 7) @@ -204,10 +195,8 @@ func TestBlockStateCacheWriteLogPerTxNum(t *testing.T) { addr := accounts.InternAddress([20]byte{0x42}) slot := accounts.InternKey([32]byte{0x04}) - oldVal := []byte{0x3f, 0x2f} newVal := []byte{0x7c, 0x1f} - cache.PutCommittedStorage(addr, slot, oldVal) cache.WriteStorage(addr, slot, newVal, 42) require.Len(t, cache.writeLog, 1) @@ -221,8 +210,4 @@ func TestBlockStateCacheWriteLogPerTxNum(t *testing.T) { val, ok := cache.GetCurrentStorage(addr, slot) require.True(t, ok) assert.Equal(t, newVal, val) - - committed, ok := cache.GetCommittedStorage(addr, slot) - require.True(t, ok) - assert.Equal(t, oldVal, committed) }