fix block pool init head selection

the head state is not necessarily saved in the database, so we need to
make sure we update things to the correct place
This commit is contained in:
Jacek Sieka 2020-01-15 12:35:54 +01:00 committed by tersec
parent b49003988a
commit 860be026e1
4 changed files with 30 additions and 27 deletions

View File

@ -173,8 +173,9 @@ type
data*: HashedBeaconState
blck*: BlockRef ##\
## The block associated with the state found in data - in particular,
## blck.state_root == rdata.root
## The block associated with the state found in data - normally
## `blck.state_root == data.root` but the state might have been advanced
## further with empty slots invalidating this condition.
BlockSlot* = object
## Unique identifier for a particular fork and time in the block chain -

View File

@ -159,7 +159,7 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
var
blocks = {tailRef.root: tailRef}.toTable()
latestStateRoot = Option[Eth2Digest]()
latestStateRoot = Option[tuple[stateRoot: Eth2Digest, blckRef: BlockRef]]()
headRef: BlockRef
if headRoot != tailRoot:
@ -183,13 +183,18 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
trace "Populating block pool", key = curRef.root, val = curRef
if latestStateRoot.isNone() and db.containsState(blck.message.state_root):
latestStateRoot = some(blck.message.state_root)
latestStateRoot = some((blck.message.state_root, curRef))
doAssert curRef == tailRef,
"head block does not lead to tail, database corrupt?"
else:
headRef = tailRef
if latestStateRoot.isNone():
doAssert db.containsState(tailBlock.message.state_root),
"state data missing for tail block, database corrupt?"
latestStateRoot = some((tailBlock.message.state_root, tailRef))
var blocksBySlot = initTable[Slot, seq[BlockRef]]()
for _, b in tables.pairs(blocks):
let slot = db.getBlock(b.root).get().message.slot
@ -199,24 +204,15 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
# many live beaconstates on the stack...
var tmpState = new Option[BeaconState]
# We're only saving epoch boundary states in the database right now, so when
# we're loading the head block, the corresponding state does not necessarily
# exist in the database - we'll load this latest state we know about and use
# that as finalization point.
tmpState[] = db.getState(latestStateRoot.get().stateRoot)
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().message.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
tmpState[] = db.getState(headStateRoot)
let
finalizedHead =
headRef.findAncestorBySlot(
tmpState[].get().finalized_checkpoint.epoch.compute_start_slot_at_epoch())
finalizedSlot =
tmpState[].get().current_justified_checkpoint.epoch.compute_start_slot_at_epoch()
finalizedHead = headRef.findAncestorBySlot(finalizedSlot)
justifiedSlot =
tmpState[].get().current_justified_checkpoint.epoch.compute_start_slot_at_epoch()
justifiedHead = headRef.findAncestorBySlot(justifiedSlot)
@ -244,8 +240,11 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
)
res.headState = StateData(
data: HashedBeaconState(data: tmpState[].get(), root: headStateRoot),
blck: headRef)
data: HashedBeaconState(
data: tmpState[].get(), root: latestStateRoot.get().stateRoot),
blck: latestStateRoot.get().blckRef)
res.updateStateData(res.headState, BlockSlot(blck: head.blck, slot: head.blck.slot))
res.tmpState = res.headState
tmpState[] = db.getState(justifiedStateRoot)
@ -253,7 +252,6 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
data: HashedBeaconState(data: tmpState[].get(), root: justifiedStateRoot),
blck: justifiedHead.blck)
res
proc addSlotMapping(pool: BlockPool, br: BlockRef) =
@ -925,6 +923,7 @@ proc preInit*(
let
blockRoot = hash_tree_root(signedBlock.message)
doAssert signedBlock.message.state_root == hash_tree_root(state)
notice "New database from snapshot",
blockRoot = shortLog(blockRoot),
stateRoot = shortLog(signedBlock.message.state_root),

View File

@ -354,7 +354,7 @@ type
# TODO to be replaced with some magic hash caching
HashedBeaconState* = object
data*: BeaconState
root*: Eth2Digest # hash_tree_root (not signing_root!)
root*: Eth2Digest # hash_tree_root(data)
StateCache* = object
beacon_committee_cache*:

View File

@ -92,7 +92,7 @@ when const_preset == "minimal": # Too much stack space used on mainnet
b1 = addBlock(state, pool.tail.root, BeaconBlockBody())
b1Root = hash_tree_root(b1.message)
b2 = addBlock(state, b1Root, BeaconBlockBody())
b2Root = hash_tree_root(b2.message)
b2Root {.used.} = hash_tree_root(b2.message)
timedTest "getRef returns nil for missing blocks":
check:
@ -147,7 +147,7 @@ when const_preset == "minimal": # Too much stack space used on mainnet
toSeq(pool.blockRootsForSlot(b1.message.slot)) == @[b1Root]
toSeq(pool.blockRootsForSlot(b2.message.slot)) == @[b2Root]
db.putHeadBlock(b2Root)
pool.updateHead(b2Get.get().refs)
# The heads structure should have been updated to contain only the new
# b2 head
@ -159,6 +159,9 @@ when const_preset == "minimal": # Too much stack space used on mainnet
pool2 = BlockPool.init(db)
check:
# ensure we loaded the correct head state
pool2.head.blck.root == b2Root
hash_tree_root(pool2.headState.data.data) == b2.message.state_root
pool2.get(b1Root).isSome()
pool2.get(b2Root).isSome()