Skip to content
23 changes: 18 additions & 5 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,16 +480,22 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
valCache := eth2wrap.NewValidatorCache(eth2Cl, eth2Pubkeys)
eth2Cl.SetValidatorCache(valCache.GetByHead)

firstValCacheRefresh := true
firstCacheRefresh := true
refreshedBySlot := true

// Setup duties cache, refreshing it every epoch.
dutiesCache := eth2wrap.NewDutiesCache(eth2Cl, []eth2p0.ValidatorIndex{})
eth2Cl.SetDutiesCache(dutiesCache.ProposerDutiesCache, dutiesCache.AttesterDutiesCache, dutiesCache.SyncCommDutiesCache)

sseListener.SubscribeChainReorgEvent(dutiesCache.InvalidateCache)

Comment on lines +486 to +491
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DutiesCache is created and hooked into the client, but it is never trimmed on epoch boundaries (only validator cache is trimmed). Since DutiesCache retains maps keyed by epoch, this will grow unbounded over time unless Trim is called periodically (e.g., alongside valCache.Trim() on refresh).

Copilot uses AI. Check for mistakes.
var fvcrLock sync.RWMutex

shouldUpdateCache := func(slot core.Slot, lock *sync.RWMutex) bool {
lock.RLock()
defer lock.RUnlock()

if !slot.FirstInEpoch() && !firstValCacheRefresh && refreshedBySlot {
if !slot.FirstInEpoch() && !firstCacheRefresh && refreshedBySlot {
return false
}

Expand All @@ -504,7 +510,7 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
fvcrLock.Lock()
defer fvcrLock.Unlock()

ctx = log.WithCtx(ctx, z.Bool("first_refresh", firstValCacheRefresh))
ctx = log.WithCtx(ctx, z.Bool("first_refresh", firstCacheRefresh))

log.Info(ctx, "Refreshing validator cache")

Expand All @@ -518,14 +524,21 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,

valCache.Trim()

_, _, refresh, err := valCache.GetBySlot(ctx, slotToFetch)
activeValidators, _, refresh, err := valCache.GetBySlot(ctx, slotToFetch)
if err != nil {
log.Error(ctx, "Failed to refresh validator cache", err)
return err
}

vIdxs := []eth2p0.ValidatorIndex{}
for idx := range activeValidators {
vIdxs = append(vIdxs, idx)
}

dutiesCache.UpdateCacheIndices(ctx, vIdxs)

refreshedBySlot = refresh
firstValCacheRefresh = false
firstCacheRefresh = false

return nil
})
Expand Down
Loading
Loading