115 lines
3.2 KiB
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
|