2019-02-28 21:21:29 +00:00
|
|
|
import
|
2019-03-13 22:59:20 +00:00
|
|
|
bitops, chronicles, options, sequtils, tables,
|
2019-02-28 21:21:29 +00:00
|
|
|
ssz, beacon_chain_db, state_transition, extras,
|
2019-03-13 22:59:20 +00:00
|
|
|
beacon_node_types,
|
|
|
|
spec/[crypto, datatypes, digest, helpers]
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
proc link(parent, child: BlockRef) =
|
|
|
|
doAssert (not (parent.root == Eth2Digest() or child.root == Eth2Digest())),
|
|
|
|
"blocks missing root!"
|
|
|
|
doAssert parent.root != child.root, "self-references not allowed"
|
|
|
|
|
|
|
|
child.parent = parent
|
|
|
|
parent.children.add(child)
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
proc init*(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef =
|
|
|
|
BlockRef(
|
|
|
|
root: root,
|
|
|
|
slot: slot
|
|
|
|
)
|
|
|
|
|
|
|
|
proc init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef =
|
|
|
|
BlockRef.init(root, blck.slot)
|
|
|
|
|
2019-03-14 13:33:56 +00:00
|
|
|
proc findAncestorBySlot*(blck: BlockRef, slot: Slot): BlockRef =
|
|
|
|
## Find the first ancestor that has a slot number less than or equal to `slot`
|
2019-03-22 10:57:19 +00:00
|
|
|
assert(not blck.isNil)
|
2019-03-13 22:59:20 +00:00
|
|
|
result = blck
|
|
|
|
|
2019-03-22 10:57:19 +00:00
|
|
|
while result.parent != nil and result.slot > slot:
|
2019-03-13 22:59:20 +00:00
|
|
|
result = result.parent
|
|
|
|
|
2019-03-22 10:57:19 +00:00
|
|
|
assert(not result.isNil)
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
|
|
|
# TODO we require that the db contains both a head and a tail block -
|
|
|
|
# asserting here doesn't seem like the right way to go about it however..
|
|
|
|
|
|
|
|
let
|
|
|
|
tail = db.getTailBlock()
|
|
|
|
head = db.getHeadBlock()
|
|
|
|
|
|
|
|
doAssert tail.isSome(), "Missing tail block, database corrupt?"
|
|
|
|
doAssert head.isSome(), "Missing head block, database corrupt?"
|
|
|
|
|
|
|
|
let
|
|
|
|
tailRoot = tail.get()
|
2019-03-13 22:59:20 +00:00
|
|
|
tailBlock = db.getBlock(tailRoot).get()
|
|
|
|
tailRef = BlockRef.init(tailRoot, tailBlock)
|
|
|
|
headRoot = head.get()
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
var
|
|
|
|
blocks = {tailRef.root: tailRef}.toTable()
|
|
|
|
latestStateRoot = Option[Eth2Digest]()
|
|
|
|
headStateBlock = tailRef
|
|
|
|
headRef: BlockRef
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if headRoot != tailRoot:
|
|
|
|
var curRef: BlockRef
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
for root, blck in db.getAncestors(headRoot):
|
2019-02-28 21:21:29 +00:00
|
|
|
if root == tailRef.root:
|
2019-03-13 23:04:43 +00:00
|
|
|
doAssert(not curRef.isNil)
|
2019-02-28 21:21:29 +00:00
|
|
|
link(tailRef, curRef)
|
|
|
|
curRef = curRef.parent
|
|
|
|
break
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
let newRef = BlockRef.init(root, blck)
|
2019-02-28 21:21:29 +00:00
|
|
|
if curRef == nil:
|
2019-03-13 22:59:20 +00:00
|
|
|
curRef = newRef
|
|
|
|
headRef = newRef
|
2019-02-28 21:21:29 +00:00
|
|
|
else:
|
2019-03-13 22:59:20 +00:00
|
|
|
link(newRef, curRef)
|
2019-02-28 21:21:29 +00:00
|
|
|
curRef = curRef.parent
|
|
|
|
blocks[curRef.root] = curRef
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
if latestStateRoot.isNone() and db.containsState(blck.state_root):
|
|
|
|
latestStateRoot = some(blck.state_root)
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
doAssert curRef == tailRef,
|
|
|
|
"head block does not lead to tail, database corrupt?"
|
2019-03-13 22:59:20 +00:00
|
|
|
else:
|
|
|
|
headRef = tailRef
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-11 15:38:36 +00:00
|
|
|
var blocksBySlot = initTable[uint64, seq[BlockRef]]()
|
|
|
|
for _, b in tables.pairs(blocks):
|
|
|
|
let slot = db.getBlock(b.root).get().slot
|
2019-03-12 22:21:32 +00:00
|
|
|
blocksBySlot.mgetOrPut(slot.uint64, @[]).add(b)
|
2019-03-11 15:38:36 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
let
|
|
|
|
# The head state is necessary to find out what we considered to be the
|
|
|
|
# finalized epoch last time we saved something.
|
|
|
|
headStateRoot =
|
|
|
|
if latestStateRoot.isSome():
|
|
|
|
latestStateRoot.get()
|
|
|
|
else:
|
|
|
|
db.getBlock(tailRef.root).get().state_root
|
|
|
|
|
|
|
|
# TODO right now, because we save a state at every epoch, this *should*
|
|
|
|
# be the latest justified state or newer, meaning it's enough for
|
|
|
|
# establishing what we consider to be the finalized head. This logic
|
|
|
|
# will need revisiting however
|
|
|
|
headState = db.getState(headStateRoot).get()
|
|
|
|
finalizedHead =
|
|
|
|
headRef.findAncestorBySlot(headState.finalized_epoch.get_epoch_start_slot())
|
|
|
|
justifiedHead =
|
2019-03-16 19:52:37 +00:00
|
|
|
headRef.findAncestorBySlot(headState.current_justified_epoch.get_epoch_start_slot())
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
doAssert justifiedHead.slot >= finalizedHead.slot,
|
|
|
|
"justified head comes before finalized head - database corrupt?"
|
|
|
|
|
|
|
|
# TODO what about ancestors? only some special blocks are
|
|
|
|
# finalized / justified but to find out exactly which ones, we would have
|
|
|
|
# to replay state transitions from tail to head and note each one...
|
|
|
|
finalizedHead.finalized = true
|
|
|
|
justifiedHead.justified = true
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
BlockPool(
|
|
|
|
pending: initTable[Eth2Digest, BeaconBlock](),
|
|
|
|
unresolved: initTable[Eth2Digest, UnresolvedBlock](),
|
|
|
|
blocks: blocks,
|
2019-03-11 15:38:36 +00:00
|
|
|
blocksBySlot: blocksBySlot,
|
2019-03-13 22:59:20 +00:00
|
|
|
tail: tailRef,
|
|
|
|
head: headRef,
|
|
|
|
finalizedHead: finalizedHead,
|
2019-02-28 21:21:29 +00:00
|
|
|
db: db
|
|
|
|
)
|
|
|
|
|
2019-03-11 15:38:36 +00:00
|
|
|
proc addSlotMapping(pool: BlockPool, slot: uint64, br: BlockRef) =
|
|
|
|
proc addIfMissing(s: var seq[BlockRef], v: BlockRef) =
|
|
|
|
if v notin s:
|
|
|
|
s.add(v)
|
|
|
|
pool.blocksBySlot.mgetOrPut(slot, @[]).addIfMissing(br)
|
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
proc updateState*(
|
2019-03-14 13:33:56 +00:00
|
|
|
pool: BlockPool, state: var StateData, blck: BlockRef, slot: Slot) {.gcsafe.}
|
2019-03-08 16:40:17 +00:00
|
|
|
|
|
|
|
proc add*(
|
|
|
|
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
|
|
|
blck: BeaconBlock): bool {.gcsafe.} =
|
2019-02-28 21:21:29 +00:00
|
|
|
## return false indicates that the block parent was missing and should be
|
|
|
|
## fetched
|
2019-03-08 16:40:17 +00:00
|
|
|
## the state parameter may be updated to include the given block, if
|
|
|
|
## everything checks out
|
|
|
|
# TODO reevaluate passing the state in like this
|
|
|
|
# TODO reevaluate this API - it's pretty ugly with the bool return
|
2019-03-25 16:46:31 +00:00
|
|
|
doAssert blockRoot == hash_tree_root(blck)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
# Already seen this block??
|
|
|
|
if blockRoot in pool.blocks:
|
|
|
|
debug "Block already exists",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
2019-02-28 21:21:29 +00:00
|
|
|
blockRoot = shortLog(blockRoot)
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
# If the block we get is older than what we finalized already, we drop it.
|
|
|
|
# One way this can happen is that we start resolving a block and finalization
|
|
|
|
# happens in the meantime - the block we requested will then be stale
|
|
|
|
# by the time it gets here.
|
|
|
|
if blck.slot <= pool.finalizedHead.slot:
|
2019-02-28 21:21:29 +00:00
|
|
|
debug "Old block, dropping",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
2019-03-13 22:59:20 +00:00
|
|
|
tailSlot = humaneSlotNum(pool.tail.slot),
|
2019-02-28 21:21:29 +00:00
|
|
|
blockRoot = shortLog(blockRoot)
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2019-03-16 19:52:37 +00:00
|
|
|
let parent = pool.blocks.getOrDefault(blck.previous_block_root)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
if parent != nil:
|
2019-03-08 16:40:17 +00:00
|
|
|
# The block might have been in either of these - we don't want any more
|
|
|
|
# work done on its behalf
|
|
|
|
pool.unresolved.del(blockRoot)
|
|
|
|
pool.pending.del(blockRoot)
|
|
|
|
|
|
|
|
# The block is resolved, now it's time to validate it to ensure that the
|
|
|
|
# blocks we add to the database are clean for the given state
|
2019-03-14 13:33:56 +00:00
|
|
|
updateState(pool, state, parent, blck.slot - 1)
|
2019-03-08 16:40:17 +00:00
|
|
|
|
|
|
|
if not updateState(state.data, parent.root, blck, {}):
|
|
|
|
# TODO find a better way to log all this block data
|
|
|
|
notice "Invalid block",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
|
|
|
blockRoot = shortLog(blockRoot)
|
2019-03-08 16:40:17 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
let blockRef = BlockRef.init(blockRoot, blck)
|
2019-02-28 21:21:29 +00:00
|
|
|
link(parent, blockRef)
|
|
|
|
|
|
|
|
pool.blocks[blockRoot] = blockRef
|
|
|
|
|
2019-03-12 22:21:32 +00:00
|
|
|
pool.addSlotMapping(blck.slot.uint64, blockRef)
|
2019-03-11 15:38:36 +00:00
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
# Resolved blocks should be stored in database
|
|
|
|
pool.db.putBlock(blockRoot, blck)
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
# This block *might* have caused a justification - make sure we stow away
|
|
|
|
# that information:
|
|
|
|
let
|
|
|
|
justifiedBlock =
|
|
|
|
blockRef.findAncestorBySlot(
|
2019-03-16 19:52:37 +00:00
|
|
|
state.data.current_justified_epoch.get_epoch_start_slot())
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
if not justifiedBlock.justified:
|
|
|
|
info "Justified block",
|
|
|
|
justifiedBlockRoot = shortLog(justifiedBlock.root),
|
|
|
|
justifiedBlockRoot = humaneSlotnum(justifiedBlock.slot),
|
|
|
|
headBlockRoot = shortLog(blockRoot),
|
|
|
|
headBlockSlot = humaneSlotnum(blck.slot)
|
|
|
|
|
|
|
|
justifiedBlock.justified = true
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
info "Block resolved",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
|
|
|
blockRoot = shortLog(blockRoot)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
# Now that we have the new block, we should see if any of the previously
|
|
|
|
# unresolved blocks magically become resolved
|
|
|
|
# TODO there are more efficient ways of doing this, that also don't risk
|
|
|
|
# running out of stack etc
|
|
|
|
let retries = pool.pending
|
|
|
|
for k, v in retries:
|
2019-03-08 16:40:17 +00:00
|
|
|
discard pool.add(state, k, v)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
# TODO possibly, it makes sense to check the database - that would allow sync
|
|
|
|
# to simply fill up the database with random blocks the other clients
|
|
|
|
# think are useful - but, it would also risk filling the database with
|
|
|
|
# junk that's not part of the block graph
|
|
|
|
|
2019-03-16 19:52:37 +00:00
|
|
|
if blck.previous_block_root in pool.unresolved:
|
2019-02-28 21:21:29 +00:00
|
|
|
return true
|
|
|
|
|
|
|
|
# This is an unresolved block - put it on the unresolved list for now...
|
2019-03-09 04:23:14 +00:00
|
|
|
# TODO if we receive spam blocks, one heurestic to implement might be to wait
|
|
|
|
# for a couple of attestations to appear before fetching parents - this
|
|
|
|
# would help prevent using up network resources for spam - this serves
|
|
|
|
# two purposes: one is that attestations are likely to appear for the
|
|
|
|
# block only if it's valid / not spam - the other is that malicious
|
|
|
|
# validators that are not proposers can sign invalid blocks and send
|
|
|
|
# them out without penalty - but signing invalid attestations carries
|
|
|
|
# a risk of being slashed, making attestations a more valuable spam
|
|
|
|
# filter.
|
2019-02-28 21:21:29 +00:00
|
|
|
debug "Unresolved block",
|
2019-03-14 13:33:56 +00:00
|
|
|
blck = shortLog(blck),
|
2019-02-28 21:21:29 +00:00
|
|
|
blockRoot = shortLog(blockRoot)
|
|
|
|
|
2019-03-16 19:52:37 +00:00
|
|
|
pool.unresolved[blck.previous_block_root] = UnresolvedBlock()
|
2019-02-28 21:21:29 +00:00
|
|
|
pool.pending[blockRoot] = blck
|
|
|
|
|
|
|
|
false
|
|
|
|
|
|
|
|
proc get*(pool: BlockPool, blck: BlockRef): BlockData =
|
|
|
|
## Retrieve the associated block body of a block reference
|
|
|
|
doAssert (not blck.isNil), "Trying to get nil BlockRef"
|
|
|
|
|
|
|
|
let data = pool.db.getBlock(blck.root)
|
|
|
|
doAssert data.isSome, "BlockRef without backing data, database corrupt?"
|
|
|
|
|
|
|
|
BlockData(data: data.get(), refs: blck)
|
|
|
|
|
|
|
|
proc get*(pool: BlockPool, root: Eth2Digest): Option[BlockData] =
|
|
|
|
## Retrieve a resolved block reference and its associated body, if available
|
|
|
|
let refs = pool.blocks.getOrDefault(root)
|
|
|
|
|
|
|
|
if not refs.isNil:
|
|
|
|
some(pool.get(refs))
|
|
|
|
else:
|
|
|
|
none(BlockData)
|
|
|
|
|
|
|
|
proc getOrResolve*(pool: var BlockPool, root: Eth2Digest): BlockRef =
|
|
|
|
## Fetch a block ref, or nil if not found (will be added to list of
|
|
|
|
## blocks-to-resolve)
|
|
|
|
result = pool.blocks.getOrDefault(root)
|
|
|
|
|
|
|
|
if result.isNil:
|
|
|
|
pool.unresolved[root] = UnresolvedBlock()
|
|
|
|
|
2019-03-12 22:21:32 +00:00
|
|
|
iterator blockRootsForSlot*(pool: BlockPool, slot: uint64|Slot): Eth2Digest =
|
|
|
|
for br in pool.blocksBySlot.getOrDefault(slot.uint64, @[]):
|
2019-03-11 15:38:36 +00:00
|
|
|
yield br.root
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
proc checkUnresolved*(pool: var BlockPool): seq[Eth2Digest] =
|
|
|
|
## Return a list of blocks that we should try to resolve from other client -
|
|
|
|
## to be called periodically but not too often (once per slot?)
|
|
|
|
var done: seq[Eth2Digest]
|
|
|
|
|
|
|
|
for k, v in pool.unresolved.mpairs():
|
|
|
|
if v.tries > 8:
|
|
|
|
done.add(k)
|
|
|
|
else:
|
|
|
|
inc v.tries
|
|
|
|
|
|
|
|
for k in done:
|
2019-03-08 16:40:17 +00:00
|
|
|
# TODO Need to potentially remove from pool.pending - this is currently a
|
|
|
|
# memory leak here!
|
2019-02-28 21:21:29 +00:00
|
|
|
pool.unresolved.del(k)
|
|
|
|
|
|
|
|
# simple (simplistic?) exponential backoff for retries..
|
|
|
|
for k, v in pool.unresolved.pairs():
|
|
|
|
if v.tries.popcount() == 1:
|
|
|
|
result.add(k)
|
|
|
|
|
|
|
|
proc skipAndUpdateState(
|
2019-03-08 16:40:17 +00:00
|
|
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
|
|
|
afterUpdate: proc (state: BeaconState)): bool =
|
2019-03-16 19:52:37 +00:00
|
|
|
skipSlots(state, blck.previous_block_root, blck.slot - 1, afterUpdate)
|
|
|
|
let ok = updateState(state, blck.previous_block_root, blck, flags)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
afterUpdate(state)
|
|
|
|
|
|
|
|
ok
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
proc maybePutState(pool: BlockPool, state: BeaconState) =
|
|
|
|
# TODO we save state at every epoch start but never remove them - we also
|
|
|
|
# potentially save multiple states per slot if reorgs happen, meaning
|
|
|
|
# we could easily see a state explosion
|
|
|
|
if state.slot mod SLOTS_PER_EPOCH == 0:
|
|
|
|
info "Storing state",
|
|
|
|
stateSlot = humaneSlotNum(state.slot),
|
2019-03-25 16:46:31 +00:00
|
|
|
stateRoot = shortLog(hash_tree_root(state)) # TODO cache?
|
2019-03-08 16:40:17 +00:00
|
|
|
pool.db.putState(state)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
proc updateState*(
|
2019-03-14 13:33:56 +00:00
|
|
|
pool: BlockPool, state: var StateData, blck: BlockRef, slot: Slot) =
|
|
|
|
## Rewind or advance state such that it matches the given block and slot -
|
|
|
|
## this may include replaying from an earlier snapshot if blck is on a
|
|
|
|
## different branch or has advanced to a higher slot number than slot
|
|
|
|
## If slot is higher than blck.slot, replay will fill in with empty/non-block
|
|
|
|
## slots, else it is ignored
|
2019-02-28 21:21:29 +00:00
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
# We need to check the slot because the state might have moved forwards
|
|
|
|
# without blocks
|
2019-03-14 13:33:56 +00:00
|
|
|
if state.blck.root == blck.root and state.data.slot == slot:
|
2019-03-08 16:40:17 +00:00
|
|
|
return # State already at the right spot
|
|
|
|
|
2019-03-14 13:33:56 +00:00
|
|
|
var ancestors = @[pool.get(blck)]
|
|
|
|
|
|
|
|
# Common case: the last thing that was applied to the state was the parent
|
|
|
|
# of blck
|
2019-03-16 19:52:37 +00:00
|
|
|
if state.blck.root == ancestors[0].data.previous_block_root and
|
2019-03-14 13:33:56 +00:00
|
|
|
state.data.slot < blck.slot:
|
2019-03-08 16:40:17 +00:00
|
|
|
let ok = skipAndUpdateState(
|
|
|
|
state.data, ancestors[0].data, {skipValidation}) do (state: BeaconState):
|
|
|
|
pool.maybePutState(state)
|
2019-02-28 21:21:29 +00:00
|
|
|
doAssert ok, "Blocks in database should never fail to apply.."
|
|
|
|
state.blck = blck
|
|
|
|
state.root = ancestors[0].data.state_root
|
|
|
|
|
2019-03-14 13:33:56 +00:00
|
|
|
skipSlots(state.data, state.blck.root, slot) do (state: BeaconState):
|
|
|
|
pool.maybePutState(state)
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# It appears that the parent root of the proposed new block is different from
|
|
|
|
# what we expected. We will have to rewind the state to a point along the
|
|
|
|
# chain of ancestors of the new block. We will do this by loading each
|
|
|
|
# successive parent block and checking if we can find the corresponding state
|
|
|
|
# in the database.
|
|
|
|
while not ancestors[^1].refs.parent.isNil:
|
|
|
|
let parent = pool.get(ancestors[^1].refs.parent)
|
|
|
|
ancestors.add parent
|
|
|
|
|
|
|
|
if pool.db.containsState(parent.data.state_root): break
|
|
|
|
|
|
|
|
let
|
|
|
|
ancestor = ancestors[^1]
|
|
|
|
ancestorState = pool.db.getState(ancestor.data.state_root)
|
|
|
|
|
|
|
|
if ancestorState.isNone():
|
|
|
|
# TODO this should only happen if the database is corrupt - we walked the
|
|
|
|
# list of parent blocks and couldn't find a corresponding state in the
|
|
|
|
# database, which should never happen (at least we should have the
|
|
|
|
# tail state in there!)
|
|
|
|
error "Couldn't find ancestor state or block parent missing!",
|
|
|
|
blockRoot = shortLog(blck.root)
|
|
|
|
doAssert false, "Oh noes, we passed big bang!"
|
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
debug "Replaying state transitions",
|
2019-02-28 21:21:29 +00:00
|
|
|
stateSlot = humaneSlotNum(state.data.slot),
|
2019-03-08 16:40:17 +00:00
|
|
|
stateRoot = shortLog(ancestor.data.state_root),
|
2019-02-28 21:21:29 +00:00
|
|
|
prevStateSlot = humaneSlotNum(ancestorState.get().slot),
|
|
|
|
ancestors = ancestors.len
|
|
|
|
|
|
|
|
state.data = ancestorState.get()
|
|
|
|
|
|
|
|
# If we come this far, we found the state root. The last block on the stack
|
|
|
|
# is the one that produced this particular state, so we can pop it
|
|
|
|
# TODO it might be possible to use the latest block hashes from the state to
|
|
|
|
# do this more efficiently.. whatever!
|
|
|
|
|
|
|
|
# Time to replay all the blocks between then and now. We skip the one because
|
|
|
|
# it's the one that we found the state with, and it has already been
|
|
|
|
# applied
|
|
|
|
for i in countdown(ancestors.len - 2, 0):
|
|
|
|
let last = ancestors[i]
|
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
skipSlots(
|
2019-03-16 19:52:37 +00:00
|
|
|
state.data, last.data.previous_block_root,
|
2019-03-08 16:40:17 +00:00
|
|
|
last.data.slot - 1) do(state: BeaconState):
|
|
|
|
pool.maybePutState(state)
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
let ok = updateState(
|
2019-03-16 19:52:37 +00:00
|
|
|
state.data, last.data.previous_block_root, last.data, {skipValidation})
|
2019-03-08 16:40:17 +00:00
|
|
|
doAssert ok,
|
|
|
|
"We only keep validated blocks in the database, should never fail"
|
2019-02-28 21:21:29 +00:00
|
|
|
|
|
|
|
state.blck = blck
|
|
|
|
state.root = ancestors[0].data.state_root
|
|
|
|
|
2019-03-08 16:40:17 +00:00
|
|
|
pool.maybePutState(state.data)
|
|
|
|
|
2019-03-14 13:33:56 +00:00
|
|
|
skipSlots(state.data, state.blck.root, slot) do (state: BeaconState):
|
|
|
|
pool.maybePutState(state)
|
|
|
|
|
2019-02-28 21:21:29 +00:00
|
|
|
proc loadTailState*(pool: BlockPool): StateData =
|
|
|
|
## Load the state associated with the current tail in the pool
|
2019-03-13 22:59:20 +00:00
|
|
|
let stateRoot = pool.db.getBlock(pool.tail.root).get().state_root
|
2019-02-28 21:21:29 +00:00
|
|
|
StateData(
|
2019-03-13 22:59:20 +00:00
|
|
|
data: pool.db.getState(stateRoot).get(),
|
|
|
|
root: stateRoot,
|
|
|
|
blck: pool.tail
|
2019-02-28 21:21:29 +00:00
|
|
|
)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
|
|
|
|
## Update what we consider to be the current head, as given by the fork
|
|
|
|
## choice.
|
|
|
|
## The choice of head affects the choice of finalization point - the order
|
|
|
|
## of operations naturally becomes important here - after updating the head,
|
|
|
|
## blocks that were once considered potential candidates for a tree will
|
|
|
|
## now fall from grace, or no longer be considered resolved.
|
|
|
|
if pool.head == blck:
|
|
|
|
debug "No head update this time",
|
|
|
|
headBlockRoot = shortLog(blck.root),
|
|
|
|
headBlockSlot = humaneSlotNum(blck.slot)
|
2019-03-22 11:33:10 +00:00
|
|
|
|
2019-03-13 22:59:20 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
pool.head = blck
|
|
|
|
|
|
|
|
# Start off by making sure we have the right state
|
2019-03-14 13:33:56 +00:00
|
|
|
updateState(pool, state, blck, blck.slot)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
info "Updated head",
|
|
|
|
stateRoot = shortLog(state.root),
|
|
|
|
headBlockRoot = shortLog(state.blck.root),
|
2019-03-14 13:33:56 +00:00
|
|
|
stateSlot = humaneSlotNum(state.data.slot),
|
2019-03-16 19:52:37 +00:00
|
|
|
justifiedEpoch = humaneEpochNum(state.data.current_justified_epoch),
|
2019-03-14 13:33:56 +00:00
|
|
|
finalizedEpoch = humaneEpochNum(state.data.finalized_epoch)
|
2019-03-13 22:59:20 +00:00
|
|
|
|
|
|
|
let
|
|
|
|
# TODO there might not be a block at the epoch boundary - what then?
|
|
|
|
finalizedHead =
|
|
|
|
blck.findAncestorBySlot(state.data.finalized_epoch.get_epoch_start_slot())
|
|
|
|
|
|
|
|
doAssert (not finalizedHead.isNil),
|
|
|
|
"Block graph should always lead to a finalized block"
|
|
|
|
|
|
|
|
if finalizedHead != pool.finalizedHead:
|
|
|
|
info "Finalized block",
|
|
|
|
finalizedBlockRoot = shortLog(finalizedHead.root),
|
|
|
|
finalizedBlockSlot = humaneSlotNum(finalizedHead.slot),
|
|
|
|
headBlockRoot = shortLog(blck.root),
|
|
|
|
headBlockSlot = humaneSlotNum(blck.slot)
|
|
|
|
|
|
|
|
var cur = finalizedHead
|
|
|
|
while cur != pool.finalizedHead:
|
|
|
|
# Finalization means that we choose a single chain as the canonical one -
|
|
|
|
# it also means we're no longer interested in any branches from that chain
|
|
|
|
# up to the finalization point
|
|
|
|
|
|
|
|
# TODO technically, if we remove from children the gc should free the block
|
|
|
|
# because it should become orphaned, via mark&sweep if nothing else,
|
|
|
|
# though this needs verification
|
|
|
|
# TODO what about attestations? we need to drop those too, though they
|
|
|
|
# *should* be pretty harmless
|
|
|
|
# TODO remove from database as well.. here, or using some GC-like setup
|
|
|
|
# that periodically cleans it up?
|
|
|
|
for child in cur.parent.children:
|
|
|
|
if child != cur:
|
|
|
|
pool.blocks.del(child.root)
|
|
|
|
cur.parent.children = @[cur]
|
|
|
|
cur = cur.parent
|
|
|
|
|
|
|
|
pool.finalizedHead = finalizedHead
|
|
|
|
|
|
|
|
proc findLatestJustifiedBlock(
|
|
|
|
blck: BlockRef, depth: int, deepest: var tuple[depth: int, blck: BlockRef]) =
|
|
|
|
if blck.justified and depth > deepest.depth:
|
|
|
|
deepest = (depth, blck)
|
|
|
|
|
|
|
|
for child in blck.children:
|
|
|
|
findLatestJustifiedBlock(child, depth + 1, deepest)
|
|
|
|
|
|
|
|
proc latestJustifiedBlock*(pool: BlockPool): BlockRef =
|
|
|
|
## Return the most recent block that is justified and at least as recent
|
|
|
|
## as the latest finalized block
|
|
|
|
|
|
|
|
var deepest = (0, pool.finalizedHead)
|
|
|
|
|
|
|
|
findLatestJustifiedBlock(pool.finalizedHead, 0, deepest)
|
|
|
|
|
|
|
|
deepest[1]
|