Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions common/dbg/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ var (
DisableAdaptivePin = EnvBool("DISABLE_ADAPTIVE_PIN", false)
AssertStateCache = EnvBool("ASSERT_STATE_CACHE", false)
ReadAhead = EnvBool("READ_AHEAD", true)
ReadAheadWorkers = EnvInt("READ_AHEAD_WORKERS", runtime.NumCPU())
Comment thread
taratorio marked this conversation as resolved.
ReadAheadWait = EnvBool("READ_AHEAD_WAIT", false)
ReadAheadBALCode = EnvBool("READ_AHEAD_BAL_CODE", false)
ReadAheadTxCode = EnvBool("READ_AHEAD_TX_CODE", true)
// FilesAsyncIO warms cold state .kv pages via io_uring before the mmap read, so
// a would-be blocking page fault becomes a non-blocking read that releases the
// goroutine's P. Linux + io_uring only; self-disables (reads use ordinary faults)
Expand Down
38 changes: 38 additions & 0 deletions execution/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,44 @@ func TestStateCache_AccountDeletionGatesStaleCodeFill(t *testing.T) {
require.True(t, ok, "unrelated code fills from a current view must stay admitted")
}

func TestStateCache_CodeHashHitBindsAddress(t *testing.T) {
b := 1 * datasize.MB
c := NewStateCache(b, b, b, b)
t.Cleanup(c.Close)
firstAddr, secondAddr, code := makeAddr(1), makeAddr(2), makeCode(1)
codeHash := crypto.Keccak256Hash(code)
view := c.View(FrontierFunc(func(kv.Domain) (uint64, bool) { return 100, true }))
view.Fill(kv.CodeDomain, firstAddr, code, 10)
view.SeedAddrCodeHash(secondAddr, codeHash, 11)
_, ok := c.View(nil).Get(kv.CodeDomain, secondAddr)
require.False(t, ok, "the second address must start without an addr-keyed code binding")
got, ok := view.GetCodeByAddressHash(secondAddr)
require.True(t, ok)
require.Equal(t, code, got)
got, ok = c.View(nil).Get(kv.CodeDomain, secondAddr)
require.True(t, ok, "the hash hit must populate the addr-keyed code binding")
require.Equal(t, code, got)
c.Applier().Unwind(11)
_, ok = c.View(nil).Get(kv.CodeDomain, secondAddr)
require.False(t, ok, "the derived binding must keep the mapping stamp for unwind invalidation")
}

func TestStateCache_EmptyCodeHashUsesViewFrontierStamp(t *testing.T) {
b := 1 * datasize.MB
c := NewStateCache(b, b, b, b)
t.Cleanup(c.Close)
addr := makeAddr(1)
view := c.View(FrontierFunc(func(kv.Domain) (uint64, bool) { return 100, true }))
view.SeedAddrCodeHash(addr, [32]byte{}, 4)
codeHash, txNum, ok := c.getAddrCodeHashWithTxNum(addr)
require.True(t, ok)
require.Zero(t, codeHash)
require.Equal(t, uint64(99), txNum, "a negative mapping reflects the view, not a nonexistent value's reported step")
c.Applier().Unwind(99)
_, _, ok = c.getAddrCodeHashWithTxNum(addr)
require.False(t, ok)
}

// An apply-only cache (STATE_CACHE_FILLS=false) has no fill for a lowered
// frontier to poison; wire-up code keys the aggregator forbid on this.
func TestApplyOnlyCacheReportsFillsDisabled(t *testing.T) {
Expand Down
14 changes: 11 additions & 3 deletions execution/cache/code_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,17 +364,25 @@ func (c *CodeCache) putCodeLocked(addr []byte, code []byte, keyHash [32]byte, co
// EVM-known codeHash is already known. Eviction is LRU; freshly seen addrs
// replace coldest entries.
func (c *CodeCache) GetAddrCodeHash(addr []byte) ([32]byte, bool) {
h, _, ok := c.GetAddrCodeHashWithTxNum(addr)
return h, ok
}

// GetAddrCodeHashWithTxNum is GetAddrCodeHash plus the txNum of the
// addr→codeHash mapping. A caller that uses the hash to bind cached code to
// the address must preserve this stamp so unwind invalidation remains correct.
func (c *CodeCache) GetAddrCodeHashWithTxNum(addr []byte) ([32]byte, uint64, bool) {
k := common.BytesToAddress(addr)
coh := c.coh.Snapshot()
e, ok := c.addrToCodeHash.Get(k)
if !ok {
return [32]byte{}, false
return [32]byte{}, 0, false
}
if coh.IsStale(e.txNum, e.epoch) {
c.addrToCodeHash.Remove(k)
return [32]byte{}, false
return [32]byte{}, 0, false
}
return e.hash, true
return e.hash, e.txNum, true
}

// PutAddrCodeHash records a committed-state addr → codeHash mapping. An
Expand Down
11 changes: 11 additions & 0 deletions execution/cache/code_cache_codehash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ func TestCodeCache_PutAddrCodeHashReplacesStaleEntry(t *testing.T) {
require.Equal(t, newHash, got)
}

func TestCodeCache_GetAddrCodeHashWithTxNumPreservesStamp(t *testing.T) {
c := closeOnCleanup(t, NewCodeCache(1*datasize.MB, 1*datasize.MB))
addr := makeAddr(1)
codeHash := [32]byte{1}
c.PutAddrCodeHash(addr, codeHash, 42)
gotHash, gotTxNum, ok := c.GetAddrCodeHashWithTxNum(addr)
require.True(t, ok)
require.Equal(t, codeHash, gotHash)
require.Equal(t, uint64(42), gotTxNum)
}

func TestCodeCache_GetByCodeHash_HitAfterPut(t *testing.T) {
c := closeOnCleanup(t, NewCodeCache(1*datasize.MB, 1*datasize.MB))
addr := makeAddr(1)
Expand Down
22 changes: 19 additions & 3 deletions execution/cache/state_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/c2h5oh/datasize"

"github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/common/crypto"
"github.com/erigontech/erigon/common/dbg"
"github.com/erigontech/erigon/common/log/v3"
Expand Down Expand Up @@ -204,6 +205,14 @@ func (c *StateCache) getAddrCodeHash(addr []byte) ([32]byte, bool) {
return cc.GetAddrCodeHash(addr)
}

func (c *StateCache) getAddrCodeHashWithTxNum(addr []byte) ([32]byte, uint64, bool) {
cc, ok := c.caches[kv.CodeDomain].(*CodeCache)
if !ok {
return [32]byte{}, 0, false
}
return cc.GetAddrCodeHashWithTxNum(addr)
}

// seedAddrCodeHash conditionally records an addr → codeHash mapping.
// The mapping derives from an account record, so admission checks the accounts
// frontier even though the mapping lives in the code cache.
Expand Down Expand Up @@ -271,18 +280,25 @@ func (c *StateCache) fillIfFresh(domain kv.Domain, key []byte, value []byte, rea
// negatives are not cached here: "no code" is cached at the addr→codeHash
// mapping instead (the zero-hash sentinel seeded by SeedAddrCodeHash).
func (c *StateCache) fillCodeIfFresh(key []byte, value []byte, readTxNum, visibleEnd, accountsVisibleEnd uint64) {
codeCache, ok := c.caches[kv.CodeDomain].(*CodeCache)
if !ok || len(value) == 0 {
if len(value) == 0 {
return
}
codeHash := crypto.Keccak256(value)
cloned := bytes.Clone(value)
c.fillCodeWithHashIfFresh(key, cloned, codeHash, readTxNum, visibleEnd, accountsVisibleEnd)
}

func (c *StateCache) fillCodeWithHashIfFresh(key, value, codeHash []byte, readTxNum, visibleEnd, accountsVisibleEnd uint64) {
codeCache, ok := c.caches[kv.CodeDomain].(*CodeCache)
if !ok || len(value) == 0 || len(codeHash) != len(common.Hash{}) {
return
}
c.admissionMu.RLock()
defer c.admissionMu.RUnlock()
if visibleEnd < c.appliedEnd[kv.CodeDomain] || accountsVisibleEnd < c.appliedEnd[kv.AccountsDomain] {
return
}
codeCache.PutWithCodeHashIfAbsent(key, cloned, codeHash, readTxNum)
codeCache.PutWithCodeHashIfAbsent(key, value, codeHash, readTxNum)
}

// deleteKey removes the data for the given domain and key. Authoritative
Expand Down
41 changes: 41 additions & 0 deletions execution/cache/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,41 @@ func (v ReadView) GetAddrCodeHash(addr []byte) ([32]byte, bool) {
return v.c.getAddrCodeHash(addr)
}

// GetCodeByAddressHash resolves the durable addr→codeHash mapping and then
// probes the content-addressed code cache. On a hit it also fills the
// addr-keyed code binding with the mapping's original txNum, avoiding both a
// database read and a redundant keccak of the cached code.
func (v ReadView) GetCodeByAddressHash(addr []byte) ([]byte, bool) {
if v.c == nil {
return nil, false
}
codeHash, readTxNum, ok := v.c.getAddrCodeHashWithTxNum(addr)
if !ok || codeHash == ([32]byte{}) {
return nil, false
}
code, ok := v.c.getCodeByHash(codeHash[:])
if !ok {
return nil, false
}
v.fillCodeWithHash(addr, code, codeHash[:], readTxNum)
return code, true
}

func (v ReadView) fillCodeWithHash(addr, code, codeHash []byte, readTxNum uint64) {
if v.c == nil || v.c.disableFills || v.frontier == nil {
return
}
visibleEnd, ok := v.frontier.DomainVisibleEnd(kv.CodeDomain)
if !ok {
return
}
accountsEnd, ok := v.frontier.DomainVisibleEnd(kv.AccountsDomain)
if !ok {
return
}
v.c.fillCodeWithHashIfFresh(addr, code, codeHash, readTxNum, visibleEnd, accountsEnd)
}

// CanFill reports whether this view carries a frontier, i.e. Fill and
// SeedAddrCodeHash can admit values through it.
func (v ReadView) CanFill() bool { return v.c != nil && v.frontier != nil }
Expand Down Expand Up @@ -145,6 +180,12 @@ func (v ReadView) SeedAddrCodeHash(addr []byte, h [32]byte, txNum uint64) {
if !ok {
return
}
if h == ([32]byte{}) {
txNum = 0
if visibleEnd > 0 {
txNum = visibleEnd - 1
}
}
v.c.seedAddrCodeHash(addr, h, txNum, visibleEnd)
}

Expand Down
Loading
Loading