From 860be026e192037f177f724733530e9635c05120 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Wed, 15 Jan 2020 12:35:54 +0100 Subject: [PATCH] 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 --- beacon_chain/beacon_node_types.nim | 5 ++-- beacon_chain/block_pool.nim | 43 +++++++++++++++--------------- beacon_chain/spec/datatypes.nim | 2 +- tests/test_block_pool.nim | 7 +++-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/beacon_chain/beacon_node_types.nim b/beacon_chain/beacon_node_types.nim index 1aa81d877..18d9b9048 100644 --- a/beacon_chain/beacon_node_types.nim +++ b/beacon_chain/beacon_node_types.nim @@ -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 - diff --git a/beacon_chain/block_pool.nim b/beacon_chain/block_pool.nim index 8e3680c03..c4d7fdd53 100644 --- a/beacon_chain/block_pool.nim +++ b/beacon_chain/block_pool.nim @@ -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), diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index e6f5d7731..68c44f0ac 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -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*: diff --git a/tests/test_block_pool.nim b/tests/test_block_pool.nim index 1c9b27f8f..81fc0b988 100644 --- a/tests/test_block_pool.nim +++ b/tests/test_block_pool.nim @@ -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()