nimbus-eth2/beacon_chain/rpc/state_ttl_cache.nim

115 lines
3.2 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
chronos,
chronicles,
../spec/beaconstate,
../consensus_object_pools/blockchain_dag
type
CacheEntry = ref object
state: ref ForkedHashedBeaconState
lastUsed: Moment
# This is ref object because we need to capture it by
# reference in the `scheduleEntryExpiration` function.
StateTtlCache* = ref object
entries: seq[CacheEntry]
ttl: Duration
const
slotDifferenceForCacheHit = 5 * SLOTS_PER_EPOCH
logScope:
topics = "state_ttl_cache"
proc init*(T: type StateTtlCache,
cacheSize: Natural,
cacheTtl: Duration): T =
doAssert cacheSize > 0
StateTtlCache(
entries: newSeq[CacheEntry](cacheSize),
ttl: cacheTtl)
proc scheduleEntryExpiration(cache: StateTtlCache,
entryIdx: int) =
proc removeElement(arg: pointer) =
if cache.entries[entryIdx] == nil:
return
let expirationTime = cache.entries[entryIdx].lastUsed + cache.ttl
if expirationTime > Moment.now:
return
cache.entries[entryIdx] = nil
debug "Cached REST state expired", index = entryIdx
discard setTimer(Moment.now + cache.ttl, removeElement)
proc add*(cache: StateTtlCache, state: ref ForkedHashedBeaconState) =
var
now = Moment.now
lruTime = now
index = -1
for i in 0 ..< cache.entries.len:
if cache.entries[i] == nil:
index = i
break
if cache.entries[i].lastUsed <= lruTime:
index = i
lruTime = cache.entries[i].lastUsed
doAssert index != -1
cache.entries[index] = CacheEntry(state: state, lastUsed: now)
debug "Cached REST state added", index = index
cache.scheduleEntryExpiration(index)
proc getClosestState*(
cache: StateTtlCache, dag: ChainDAGRef,
bsi: BlockSlotId): ref ForkedHashedBeaconState =
var
bestSlotDifference = Slot.high
index = -1
for i in 0 ..< cache.entries.len:
if cache.entries[i] == nil:
continue
let stateSlot = getStateField(cache.entries[i][].state[], slot)
if stateSlot > bsi.slot:
# We can use only states that can be advanced forward in time.
continue
let slotDifference = bsi.slot - stateSlot
if slotDifference > slotDifferenceForCacheHit:
# The state is too old to be useful as a rewind starting point.
continue
var cur = bsi
for j in 0 ..< slotDifference:
cur = dag.parentOrSlot(cur).valueOr:
break
if not cache.entries[i].state[].matches_block(cur.bid.root):
# The cached state and the requested BlockSlot are at different branches
# of history.
continue
if slotDifference < bestSlotDifference:
bestSlotDifference = slotDifference.Slot
index = i
if index == -1:
return nil
cache.entries[index].lastUsed = Moment.now
cache.scheduleEntryExpiration(index)
return cache.entries[index].state