State/block pruning

This commit is contained in:
Yuriy Glukhov 2019-11-22 16:14:13 +02:00 committed by zah
parent 76d3e74b02
commit 777b3f4e29
5 changed files with 82 additions and 17 deletions

View File

@ -86,6 +86,12 @@ proc putStateRoot*(db: BeaconChainDB, root: Eth2Digest, slot: Slot,
proc putBlock*(db: BeaconChainDB, value: BeaconBlock) =
db.putBlock(signing_root(value), value)
proc delBlock*(db: BeaconChainDB, key: Eth2Digest) =
db.backend.del(subkey(BeaconBlock, key))
proc delState*(db: BeaconChainDB, key: Eth2Digest) =
db.backend.del(subkey(BeaconState, key))
proc putHeadBlock*(db: BeaconChainDB, key: Eth2Digest) =
db.backend.put(subkey(kHeadBlock), key.data) # TODO head block?

View File

@ -118,7 +118,7 @@ type
## Tree of blocks pointing back to a finalized block on the chain we're
## interested in - we call that block the tail
blocksBySlot*: Table[uint64, seq[BlockRef]]
blocksBySlot*: Table[Slot, seq[BlockRef]]
tail*: BlockRef ##\
## The earliest finalized block we know about

View File

@ -90,10 +90,10 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
else:
headRef = tailRef
var blocksBySlot = initTable[uint64, seq[BlockRef]]()
var blocksBySlot = initTable[Slot, seq[BlockRef]]()
for _, b in tables.pairs(blocks):
let slot = db.getBlock(b.root).get().slot
blocksBySlot.mgetOrPut(slot.uint64, @[]).add(b)
blocksBySlot.mgetOrPut(slot, @[]).add(b)
let
# The head state is necessary to find out what we considered to be the
@ -132,11 +132,21 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
heads: @[head]
)
func addSlotMapping(pool: BlockPool, slot: uint64, br: BlockRef) =
proc addSlotMapping(pool: BlockPool, br: BlockRef) =
proc addIfMissing(s: var seq[BlockRef], v: BlockRef) =
if v notin s:
s.add(v)
pool.blocksBySlot.mgetOrPut(slot, @[]).addIfMissing(br)
pool.blocksBySlot.mgetOrPut(br.slot, @[]).addIfMissing(br)
proc delSlotMapping(pool: BlockPool, br: BlockRef) =
var blks = pool.blocksBySlot.getOrDefault(br.slot)
if blks.len != 0:
let i = blks.find(br)
if i >= 0: blks.del(i)
if blks.len == 0:
pool.blocksBySlot.del(br.slot)
else:
pool.blocksBySlot[br.slot] = blks
proc updateStateData*(
pool: BlockPool, state: var StateData, bs: BlockSlot) {.gcsafe.}
@ -155,7 +165,7 @@ proc addResolvedBlock(
pool.blocks[blockRoot] = blockRef
pool.addSlotMapping(blck.slot.uint64, blockRef)
pool.addSlotMapping(blockRef)
# Resolved blocks should be stored in database
pool.db.putBlock(blockRoot, blck)
@ -393,8 +403,8 @@ func getOrResolve*(pool: var BlockPool, root: Eth2Digest): BlockRef =
if result.isNil:
pool.missing[root] = MissingBlock(slots: 1)
iterator blockRootsForSlot*(pool: BlockPool, slot: uint64|Slot): Eth2Digest =
for br in pool.blocksBySlot.getOrDefault(slot.uint64, @[]):
iterator blockRootsForSlot*(pool: BlockPool, slot: Slot): Eth2Digest =
for br in pool.blocksBySlot.getOrDefault(slot, @[]):
yield br.root
func checkMissing*(pool: var BlockPool): seq[FetchRecord] =
@ -580,6 +590,44 @@ func isAncestorOf*(a, b: BlockRef): bool =
else:
a.isAncestorOf(b.parent)
proc delBlockAndState(pool: BlockPool, blockRoot: Eth2Digest) =
if (let blk = pool.db.getBlock(blockRoot); blk.isSome):
pool.db.delState(blk.get.stateRoot)
pool.db.delBlock(blockRoot)
proc delFinalizedStateIfNeeded(pool: BlockPool, b: BlockRef) =
# Delete finalized state for block `b` from the database, that doesn't need
# to be kept for replaying.
# TODO: Currently the protocol doesn't provide a way to request states,
# so we don't need any of the finalized states, and thus remove all of them
# (except the most recent)
if (let blk = pool.db.getBlock(b.root); blk.isSome):
pool.db.delState(blk.get.stateRoot)
proc setTailBlock(pool: BlockPool, newTail: BlockRef) =
## Advance tail block, pruning all the states and blocks with older slots
let oldTail = pool.tail
let fromSlot = oldTail.slot.uint64
let toSlot = newTail.slot.uint64 - 1
assert(toSlot > fromSlot)
for s in fromSlot .. toSlot:
for b in pool.blocksBySlot.getOrDefault(s.Slot, @[]):
pool.delBlockAndState(b.root)
b.children = @[]
b.parent = nil
pool.blocks.del(b.root)
pool.pending.del(b.root)
pool.missing.del(b.root)
pool.blocksBySlot.del(s.Slot)
pool.db.putTailBlock(newTail.root)
pool.tail = newTail
pool.addSlotMapping(newTail)
info "Tail block updated",
slot = newTail.slot,
root = shortLog(newTail.root)
proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
## Update what we consider to be the current head, as given by the fork
## choice.
@ -634,10 +682,9 @@ proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
cat = "fork_choice"
let
finalizedEpochStartSlot = state.data.data.finalized_checkpoint.epoch.compute_start_slot_at_epoch()
# TODO there might not be a block at the epoch boundary - what then?
finalizedHead =
blck.findAncestorBySlot(
state.data.data.finalized_checkpoint.epoch.compute_start_slot_at_epoch())
finalizedHead = blck.findAncestorBySlot(finalizedEpochStartSlot)
doAssert (not finalizedHead.blck.isNil),
"Block graph should always lead to a finalized block"
@ -666,6 +713,10 @@ proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
for child in cur.parent.children:
if child != cur:
pool.blocks.del(child.root)
pool.delBlockAndState(child.root)
pool.delSlotMapping(child)
else:
pool.delFinalizedStateIfNeeded(child)
cur.parent.children = @[cur]
cur = cur.parent
@ -678,6 +729,14 @@ proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
not pool.heads[n].blck.isAncestorOf(pool.finalizedHead.blck):
pool.heads.del(n)
# Calculate new tail block and set it
# New tail should be WEAK_SUBJECTIVITY_PERIOD * 2 older than finalizedHead
const tailSlotInterval = WEAK_SUBJECTVITY_PERIOD * 2
if finalizedEpochStartSlot - GENESIS_SLOT > tailSlotInterval:
let tailSlot = finalizedEpochStartSlot - tailSlotInterval
let newTail = finalizedHead.blck.findAncestorBySlot(tailSlot)
pool.setTailBlock(newTail.blck)
func latestJustifiedBlock*(pool: BlockPool): BlockSlot =
## Return the most recent block that is justified and at least as recent
## as the latest finalized block

View File

@ -67,6 +67,12 @@ const
# Not part of spec. Still useful, pending removing usage if appropriate.
ZERO_HASH* = Eth2Digest()
# Not part of spec
WEAK_SUBJECTVITY_PERIOD* =
Slot(uint64(4 * 30 * 24 * 60 * 60) div SECONDS_PER_SLOT)
# TODO: This needs revisiting.
# Why was the validator WITHDRAWAL_PERIOD altered in the spec?
template maxSize*(n: int) {.pragma.}
type

View File

@ -2,12 +2,6 @@ import
os, chronos, json_serialization,
spec/[datatypes], beacon_chain_db
const
WEAK_SUBJECTVITY_PERIOD* =
Slot(uint64(4 * 30 * 24 * 60 * 60) div SECONDS_PER_SLOT)
# TODO: This needs revisiting.
# Why was the validator WITHDRAWAL_PERIOD altered in the spec?
proc obtainTrustedStateSnapshot*(db: BeaconChainDB): Future[BeaconState] {.async.} =
# In case our latest state is too old, we must obtain a recent snapshot
# of the state from a trusted location. This is explained in detail here: