head block selection fixes (#259)
* track justified and head blocks correctly in presence of blank slots * fix new block state root * keep list of viable heads instead of walking children * disable json bitfield deserializer (needs a corresponding serializer) * fix handshake best & finalized root
This commit is contained in:
parent
46b4154ce8
commit
bd4893d2e8
|
@ -272,7 +272,7 @@ proc updateHead(node: BeaconNode, slot: Slot): BlockRef =
|
||||||
# Use head state for attestation resolution below
|
# Use head state for attestation resolution below
|
||||||
# TODO do we need to resolve attestations using all available head states?
|
# TODO do we need to resolve attestations using all available head states?
|
||||||
node.blockPool.withState(
|
node.blockPool.withState(
|
||||||
node.stateCache, BlockSlot(blck: node.blockPool.head, slot: slot)):
|
node.stateCache, BlockSlot(blck: node.blockPool.head.blck, slot: slot)):
|
||||||
# Check pending attestations - maybe we found some blocks for them
|
# Check pending attestations - maybe we found some blocks for them
|
||||||
node.attestationPool.resolve(state)
|
node.attestationPool.resolve(state)
|
||||||
|
|
||||||
|
@ -374,7 +374,7 @@ proc proposeBlock(node: BeaconNode,
|
||||||
let ok = updateState(tmpState, newBlock, {skipValidation})
|
let ok = updateState(tmpState, newBlock, {skipValidation})
|
||||||
doAssert ok # TODO: err, could this fail somehow?
|
doAssert ok # TODO: err, could this fail somehow?
|
||||||
|
|
||||||
newBlock.state_root = hash_tree_root(state)
|
newBlock.state_root = hash_tree_root(tmpState)
|
||||||
|
|
||||||
let blockRoot = signed_root(newBlock)
|
let blockRoot = signed_root(newBlock)
|
||||||
|
|
||||||
|
@ -413,7 +413,7 @@ proc onAttestation(node: BeaconNode, attestation: Attestation) =
|
||||||
# the attestation for some of the check? Consider interop with block
|
# the attestation for some of the check? Consider interop with block
|
||||||
# production!
|
# production!
|
||||||
node.blockPool.withState(node.stateCache,
|
node.blockPool.withState(node.stateCache,
|
||||||
BlockSlot(blck: node.blockPool.head, slot: node.beaconClock.now().toSlot())):
|
BlockSlot(blck: node.blockPool.head.blck, slot: node.beaconClock.now().toSlot())):
|
||||||
node.attestationPool.add(state, attestation)
|
node.attestationPool.add(state, attestation)
|
||||||
|
|
||||||
proc onBeaconBlock(node: BeaconNode, blck: BeaconBlock) =
|
proc onBeaconBlock(node: BeaconNode, blck: BeaconBlock) =
|
||||||
|
@ -452,7 +452,7 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||||
return
|
return
|
||||||
|
|
||||||
let attestationHead = head.findAncestorBySlot(slot)
|
let attestationHead = head.findAncestorBySlot(slot)
|
||||||
if head != attestationHead:
|
if head != attestationHead.blck:
|
||||||
# In rare cases, such as when we're busy syncing or just slow, we'll be
|
# In rare cases, such as when we're busy syncing or just slow, we'll be
|
||||||
# attesting to a past state - we must then recreate the world as it looked
|
# attesting to a past state - we must then recreate the world as it looked
|
||||||
# like back then
|
# like back then
|
||||||
|
@ -462,7 +462,7 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||||
attestationSlot = humaneSlotNum(slot)
|
attestationSlot = humaneSlotNum(slot)
|
||||||
|
|
||||||
debug "Checking attestations",
|
debug "Checking attestations",
|
||||||
attestationHeadRoot = shortLog(attestationHead.root),
|
attestationHeadRoot = shortLog(attestationHead.blck.root),
|
||||||
attestationSlot = humaneSlotNum(slot)
|
attestationSlot = humaneSlotNum(slot)
|
||||||
|
|
||||||
|
|
||||||
|
@ -474,8 +474,7 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
|
||||||
# We need to run attestations exactly for the slot that we're attesting to.
|
# We need to run attestations exactly for the slot that we're attesting to.
|
||||||
# In case blocks went missing, this means advancing past the latest block
|
# In case blocks went missing, this means advancing past the latest block
|
||||||
# using empty slots as fillers.
|
# using empty slots as fillers.
|
||||||
node.blockPool.withState(
|
node.blockPool.withState(node.stateCache, attestationHead):
|
||||||
node.stateCache, BlockSlot(blck: attestationHead, slot: slot)):
|
|
||||||
for crosslink_committee in get_crosslink_committees_at_slot(state, slot):
|
for crosslink_committee in get_crosslink_committees_at_slot(state, slot):
|
||||||
for i, validatorIdx in crosslink_committee.committee:
|
for i, validatorIdx in crosslink_committee.committee:
|
||||||
let validator = node.getAttachedValidator(state, validatorIdx)
|
let validator = node.getAttachedValidator(state, validatorIdx)
|
||||||
|
|
|
@ -171,15 +171,18 @@ type
|
||||||
tail*: BlockRef ##\
|
tail*: BlockRef ##\
|
||||||
## The earliest finalized block we know about
|
## The earliest finalized block we know about
|
||||||
|
|
||||||
head*: BlockRef ##\
|
head*: Head ##\
|
||||||
## The latest block we know about, that's been chosen as a head by the fork
|
## The latest block we know about, that's been chosen as a head by the fork
|
||||||
## choice rule
|
## choice rule
|
||||||
|
|
||||||
finalizedHead*: BlockRef ##\
|
finalizedHead*: BlockSlot ##\
|
||||||
## The latest block that was finalized according to the block in head
|
## The latest block that was finalized according to the block in head
|
||||||
|
## Ancestors of this block are guaranteed to have 1 child only.
|
||||||
|
|
||||||
db*: BeaconChainDB
|
db*: BeaconChainDB
|
||||||
|
|
||||||
|
heads*: seq[Head]
|
||||||
|
|
||||||
MissingBlock* = object
|
MissingBlock* = object
|
||||||
slots*: uint64 # number of slots that are suspected missing
|
slots*: uint64 # number of slots that are suspected missing
|
||||||
tries*: int
|
tries*: int
|
||||||
|
@ -196,17 +199,10 @@ type
|
||||||
## Not nil, except for the tail
|
## Not nil, except for the tail
|
||||||
|
|
||||||
children*: seq[BlockRef]
|
children*: seq[BlockRef]
|
||||||
|
# TODO do we strictly need this?
|
||||||
|
|
||||||
slot*: Slot # TODO could calculate this by walking to root, but..
|
slot*: Slot # TODO could calculate this by walking to root, but..
|
||||||
|
|
||||||
justified*: bool ##\
|
|
||||||
## True iff there exists a descendant of this block that generates a state
|
|
||||||
## that points back to this block in its `justified_epoch` field.
|
|
||||||
finalized*: bool ##\
|
|
||||||
## True iff there exists a descendant of this block that generates a state
|
|
||||||
## that points back to this block in its `finalized_epoch` field.
|
|
||||||
## Ancestors of this block are guaranteed to have 1 child only.
|
|
||||||
|
|
||||||
BlockData* = object
|
BlockData* = object
|
||||||
## Body and graph in one
|
## Body and graph in one
|
||||||
|
|
||||||
|
@ -236,6 +232,10 @@ type
|
||||||
blck*: BlockRef
|
blck*: BlockRef
|
||||||
slot*: Slot
|
slot*: Slot
|
||||||
|
|
||||||
|
Head* = object
|
||||||
|
blck*: BlockRef
|
||||||
|
justified*: BlockSlot
|
||||||
|
|
||||||
# #############################################
|
# #############################################
|
||||||
#
|
#
|
||||||
# Validator Pool
|
# Validator Pool
|
||||||
|
|
|
@ -27,32 +27,32 @@ proc init*(T: type BlockRef, root: Eth2Digest, slot: Slot): BlockRef =
|
||||||
proc init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef =
|
proc init*(T: type BlockRef, root: Eth2Digest, blck: BeaconBlock): BlockRef =
|
||||||
BlockRef.init(root, blck.slot)
|
BlockRef.init(root, blck.slot)
|
||||||
|
|
||||||
proc findAncestorBySlot*(blck: BlockRef, slot: Slot): BlockRef =
|
proc findAncestorBySlot*(blck: BlockRef, slot: Slot): BlockSlot =
|
||||||
## Find the first ancestor that has a slot number less than or equal to `slot`
|
## Find the first ancestor that has a slot number less than or equal to `slot`
|
||||||
assert(not blck.isNil)
|
assert(not blck.isNil)
|
||||||
result = blck
|
var ret = blck
|
||||||
|
|
||||||
while result.parent != nil and result.slot > slot:
|
while ret.parent != nil and ret.slot > slot:
|
||||||
result = result.parent
|
ret = ret.parent
|
||||||
|
|
||||||
assert(not result.isNil)
|
BlockSlot(blck: ret, slot: slot)
|
||||||
|
|
||||||
proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
||||||
# TODO we require that the db contains both a head and a tail block -
|
# 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..
|
# asserting here doesn't seem like the right way to go about it however..
|
||||||
|
|
||||||
let
|
let
|
||||||
tail = db.getTailBlock()
|
tailBlockRoot = db.getTailBlock()
|
||||||
head = db.getHeadBlock()
|
headBlockRoot = db.getHeadBlock()
|
||||||
|
|
||||||
doAssert tail.isSome(), "Missing tail block, database corrupt?"
|
doAssert tailBlockRoot.isSome(), "Missing tail block, database corrupt?"
|
||||||
doAssert head.isSome(), "Missing head block, database corrupt?"
|
doAssert headBlockRoot.isSome(), "Missing head block, database corrupt?"
|
||||||
|
|
||||||
let
|
let
|
||||||
tailRoot = tail.get()
|
tailRoot = tailBlockRoot.get()
|
||||||
tailBlock = db.getBlock(tailRoot).get()
|
tailBlock = db.getBlock(tailRoot).get()
|
||||||
tailRef = BlockRef.init(tailRoot, tailBlock)
|
tailRef = BlockRef.init(tailRoot, tailBlock)
|
||||||
headRoot = head.get()
|
headRoot = headBlockRoot.get()
|
||||||
|
|
||||||
var
|
var
|
||||||
blocks = {tailRef.root: tailRef}.toTable()
|
blocks = {tailRef.root: tailRef}.toTable()
|
||||||
|
@ -108,17 +108,13 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
||||||
headState = db.getState(headStateRoot).get()
|
headState = db.getState(headStateRoot).get()
|
||||||
finalizedHead =
|
finalizedHead =
|
||||||
headRef.findAncestorBySlot(headState.finalized_epoch.get_epoch_start_slot())
|
headRef.findAncestorBySlot(headState.finalized_epoch.get_epoch_start_slot())
|
||||||
justifiedHead =
|
justifiedSlot = headState.current_justified_epoch.get_epoch_start_slot()
|
||||||
headRef.findAncestorBySlot(headState.current_justified_epoch.get_epoch_start_slot())
|
justifiedHead = headRef.findAncestorBySlot(justifiedSlot)
|
||||||
|
head = Head(blck: headRef, justified: justifiedHead)
|
||||||
|
|
||||||
doAssert justifiedHead.slot >= finalizedHead.slot,
|
doAssert justifiedHead.slot >= finalizedHead.slot,
|
||||||
"justified head comes before finalized head - database corrupt?"
|
"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
|
|
||||||
|
|
||||||
BlockPool(
|
BlockPool(
|
||||||
pending: initTable[Eth2Digest, BeaconBlock](),
|
pending: initTable[Eth2Digest, BeaconBlock](),
|
||||||
|
@ -126,9 +122,10 @@ proc init*(T: type BlockPool, db: BeaconChainDB): BlockPool =
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
blocksBySlot: blocksBySlot,
|
blocksBySlot: blocksBySlot,
|
||||||
tail: tailRef,
|
tail: tailRef,
|
||||||
head: headRef,
|
head: head,
|
||||||
finalizedHead: finalizedHead,
|
finalizedHead: finalizedHead,
|
||||||
db: db
|
db: db,
|
||||||
|
heads: @[head]
|
||||||
)
|
)
|
||||||
|
|
||||||
proc addSlotMapping(pool: BlockPool, slot: uint64, br: BlockRef) =
|
proc addSlotMapping(pool: BlockPool, slot: uint64, br: BlockRef) =
|
||||||
|
@ -140,6 +137,66 @@ proc addSlotMapping(pool: BlockPool, slot: uint64, br: BlockRef) =
|
||||||
proc updateStateData*(
|
proc updateStateData*(
|
||||||
pool: BlockPool, state: var StateData, bs: BlockSlot) {.gcsafe.}
|
pool: BlockPool, state: var StateData, bs: BlockSlot) {.gcsafe.}
|
||||||
|
|
||||||
|
proc add*(
|
||||||
|
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
||||||
|
blck: BeaconBlock): BlockRef {.gcsafe.}
|
||||||
|
|
||||||
|
proc addResolvedBlock(
|
||||||
|
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
||||||
|
blck: BeaconBlock, parent: BlockRef): BlockRef =
|
||||||
|
let blockRef = BlockRef.init(blockRoot, blck)
|
||||||
|
link(parent, blockRef)
|
||||||
|
|
||||||
|
pool.blocks[blockRoot] = blockRef
|
||||||
|
|
||||||
|
pool.addSlotMapping(blck.slot.uint64, blockRef)
|
||||||
|
|
||||||
|
# Resolved blocks should be stored in database
|
||||||
|
pool.db.putBlock(blockRoot, blck)
|
||||||
|
|
||||||
|
# TODO this is a bit ugly - we update state.data outside of this function then
|
||||||
|
# set the rest here - need a blockRef to update it. Clean this up -
|
||||||
|
# hopefully it won't be necessary by the time hash caching and the rest
|
||||||
|
# is done..
|
||||||
|
doAssert state.data.slot == blockRef.slot
|
||||||
|
state.root = blck.state_root
|
||||||
|
state.blck = blockRef
|
||||||
|
|
||||||
|
# This block *might* have caused a justification - make sure we stow away
|
||||||
|
# that information:
|
||||||
|
let justifiedSlot = state.data.current_justified_epoch.get_epoch_start_slot()
|
||||||
|
|
||||||
|
var foundHead: Option[Head]
|
||||||
|
for head in pool.heads.mitems():
|
||||||
|
if head.blck.root == blck.previous_block_root:
|
||||||
|
if head.justified.slot != justifiedSlot:
|
||||||
|
head.justified = blockRef.findAncestorBySlot(justifiedSlot)
|
||||||
|
|
||||||
|
foundHead = some(head)
|
||||||
|
break
|
||||||
|
|
||||||
|
if foundHead.isNone():
|
||||||
|
foundHead = some(Head(
|
||||||
|
blck: blockRef,
|
||||||
|
justified: blockRef.findAncestorBySlot(justifiedSlot)))
|
||||||
|
pool.heads.add(foundHead.get())
|
||||||
|
|
||||||
|
info "Block resolved",
|
||||||
|
blck = shortLog(blck),
|
||||||
|
blockRoot = shortLog(blockRoot),
|
||||||
|
justifiedRoot = shortLog(foundHead.get().justified.blck.root),
|
||||||
|
justifiedSlot = humaneSlotNum(foundHead.get().justified.slot)
|
||||||
|
|
||||||
|
# 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 don't risk
|
||||||
|
# running out of stack etc
|
||||||
|
let retries = pool.pending
|
||||||
|
for k, v in retries:
|
||||||
|
discard pool.add(state, k, v)
|
||||||
|
|
||||||
|
blockRef
|
||||||
|
|
||||||
proc add*(
|
proc add*(
|
||||||
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
pool: var BlockPool, state: var StateData, blockRoot: Eth2Digest,
|
||||||
blck: BeaconBlock): BlockRef {.gcsafe.} =
|
blck: BeaconBlock): BlockRef {.gcsafe.} =
|
||||||
|
@ -171,7 +228,6 @@ proc add*(
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
let parent = pool.blocks.getOrDefault(blck.previous_block_root)
|
let parent = pool.blocks.getOrDefault(blck.previous_block_root)
|
||||||
|
|
||||||
if parent != nil:
|
if parent != nil:
|
||||||
|
@ -194,48 +250,7 @@ proc add*(
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
let blockRef = BlockRef.init(blockRoot, blck)
|
return pool.addResolvedBlock(state, blockRoot, blck, parent)
|
||||||
link(parent, blockRef)
|
|
||||||
|
|
||||||
pool.blocks[blockRoot] = blockRef
|
|
||||||
|
|
||||||
pool.addSlotMapping(blck.slot.uint64, blockRef)
|
|
||||||
|
|
||||||
# Resolved blocks should be stored in database
|
|
||||||
pool.db.putBlock(blockRoot, blck)
|
|
||||||
|
|
||||||
state.root = blck.state_root
|
|
||||||
state.blck = blockRef
|
|
||||||
|
|
||||||
# This block *might* have caused a justification - make sure we stow away
|
|
||||||
# that information:
|
|
||||||
let
|
|
||||||
justifiedBlock =
|
|
||||||
blockRef.findAncestorBySlot(
|
|
||||||
state.data.current_justified_epoch.get_epoch_start_slot())
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
info "Block resolved",
|
|
||||||
blck = shortLog(blck),
|
|
||||||
blockRoot = shortLog(blockRoot)
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
discard pool.add(state, k, v)
|
|
||||||
|
|
||||||
return blockRef
|
|
||||||
|
|
||||||
pool.pending[blockRoot] = blck
|
pool.pending[blockRoot] = blck
|
||||||
|
|
||||||
|
@ -269,8 +284,8 @@ proc add*(
|
||||||
pool.missing[blck.previous_block_root] = MissingBlock(
|
pool.missing[blck.previous_block_root] = MissingBlock(
|
||||||
slots:
|
slots:
|
||||||
# The block is at least two slots ahead - try to grab whole history
|
# The block is at least two slots ahead - try to grab whole history
|
||||||
if parentSlot > pool.head.slot:
|
if parentSlot > pool.head.blck.slot:
|
||||||
parentSlot - pool.head.slot
|
parentSlot - pool.head.blck.slot
|
||||||
else:
|
else:
|
||||||
# It's a sibling block from a branch that we're missing - fetch one
|
# It's a sibling block from a branch that we're missing - fetch one
|
||||||
# epoch at a time
|
# epoch at a time
|
||||||
|
@ -474,6 +489,14 @@ proc loadTailState*(pool: BlockPool): StateData =
|
||||||
blck: pool.tail
|
blck: pool.tail
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isAncestorOf*(a, b: BlockRef): bool =
|
||||||
|
if a == b:
|
||||||
|
true
|
||||||
|
elif a.slot >= b.slot or b.parent.isNil:
|
||||||
|
false
|
||||||
|
else:
|
||||||
|
a.isAncestorOf(b.parent)
|
||||||
|
|
||||||
proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
|
proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
|
||||||
## Update what we consider to be the current head, as given by the fork
|
## Update what we consider to be the current head, as given by the fork
|
||||||
## choice.
|
## choice.
|
||||||
|
@ -481,7 +504,7 @@ proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
|
||||||
## of operations naturally becomes important here - after updating the head,
|
## of operations naturally becomes important here - after updating the head,
|
||||||
## blocks that were once considered potential candidates for a tree will
|
## blocks that were once considered potential candidates for a tree will
|
||||||
## now fall from grace, or no longer be considered resolved.
|
## now fall from grace, or no longer be considered resolved.
|
||||||
if pool.head == blck:
|
if pool.head.blck == blck:
|
||||||
debug "No head update this time",
|
debug "No head update this time",
|
||||||
headBlockRoot = shortLog(blck.root),
|
headBlockRoot = shortLog(blck.root),
|
||||||
headBlockSlot = humaneSlotNum(blck.slot)
|
headBlockSlot = humaneSlotNum(blck.slot)
|
||||||
|
@ -490,15 +513,16 @@ proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
|
||||||
|
|
||||||
let
|
let
|
||||||
lastHead = pool.head
|
lastHead = pool.head
|
||||||
pool.head = blck
|
|
||||||
pool.db.putHeadBlock(blck.root)
|
pool.db.putHeadBlock(blck.root)
|
||||||
|
|
||||||
# Start off by making sure we have the right state
|
# Start off by making sure we have the right state
|
||||||
updateStateData(pool, state, BlockSlot(blck: blck, slot: blck.slot))
|
updateStateData(pool, state, BlockSlot(blck: blck, slot: blck.slot))
|
||||||
|
let justifiedSlot = state.data.current_justified_epoch.get_epoch_start_slot()
|
||||||
|
pool.head = Head(blck: blck, justified: blck.findAncestorBySlot(justifiedSlot))
|
||||||
|
|
||||||
if lastHead != blck.parent:
|
if lastHead.blck != blck.parent:
|
||||||
notice "Updated head with new parent",
|
notice "Updated head with new parent",
|
||||||
lastHeadRoot = shortLog(lastHead.root),
|
lastHeadRoot = shortLog(lastHead.blck.root),
|
||||||
parentRoot = shortLog(blck.parent.root),
|
parentRoot = shortLog(blck.parent.root),
|
||||||
stateRoot = shortLog(state.root),
|
stateRoot = shortLog(state.root),
|
||||||
headBlockRoot = shortLog(state.blck.root),
|
headBlockRoot = shortLog(state.blck.root),
|
||||||
|
@ -518,67 +542,58 @@ proc updateHead*(pool: BlockPool, state: var StateData, blck: BlockRef) =
|
||||||
finalizedHead =
|
finalizedHead =
|
||||||
blck.findAncestorBySlot(state.data.finalized_epoch.get_epoch_start_slot())
|
blck.findAncestorBySlot(state.data.finalized_epoch.get_epoch_start_slot())
|
||||||
|
|
||||||
doAssert (not finalizedHead.isNil),
|
doAssert (not finalizedHead.blck.isNil),
|
||||||
"Block graph should always lead to a finalized block"
|
"Block graph should always lead to a finalized block"
|
||||||
|
|
||||||
if finalizedHead != pool.finalizedHead:
|
if finalizedHead != pool.finalizedHead:
|
||||||
info "Finalized block",
|
info "Finalized block",
|
||||||
finalizedBlockRoot = shortLog(finalizedHead.root),
|
finalizedBlockRoot = shortLog(finalizedHead.blck.root),
|
||||||
finalizedBlockSlot = humaneSlotNum(finalizedHead.slot),
|
finalizedBlockSlot = humaneSlotNum(finalizedHead.slot),
|
||||||
headBlockRoot = shortLog(blck.root),
|
headBlockRoot = shortLog(blck.root),
|
||||||
headBlockSlot = humaneSlotNum(blck.slot)
|
headBlockSlot = humaneSlotNum(blck.slot)
|
||||||
|
|
||||||
var cur = finalizedHead
|
var cur = finalizedHead.blck
|
||||||
while cur != pool.finalizedHead:
|
while cur != pool.finalizedHead.blck:
|
||||||
# Finalization means that we choose a single chain as the canonical one -
|
# 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
|
# it also means we're no longer interested in any branches from that chain
|
||||||
# up to the finalization point
|
# up to the finalization point
|
||||||
|
|
||||||
# TODO technically, if we remove from children the gc should free the block
|
# TODO technically, if we remove from children the gc should free the block
|
||||||
# because it should become orphaned, via mark&sweep if nothing else,
|
# because it should become orphaned, via mark&sweep if nothing else,
|
||||||
# though this needs verification
|
# though this needs verification
|
||||||
# TODO what about attestations? we need to drop those too, though they
|
# TODO what about attestations? we need to drop those too, though they
|
||||||
# *should* be pretty harmless
|
# *should* be pretty harmless
|
||||||
# TODO remove from database as well.. here, or using some GC-like setup
|
# TODO remove from database as well.. here, or using some GC-like setup
|
||||||
# that periodically cleans it up?
|
# that periodically cleans it up?
|
||||||
for child in cur.parent.children:
|
for child in cur.parent.children:
|
||||||
if child != cur:
|
if child != cur:
|
||||||
pool.blocks.del(child.root)
|
pool.blocks.del(child.root)
|
||||||
cur.parent.children = @[cur]
|
cur.parent.children = @[cur]
|
||||||
cur = cur.parent
|
cur = cur.parent
|
||||||
|
|
||||||
pool.finalizedHead = finalizedHead
|
pool.finalizedHead = finalizedHead
|
||||||
|
|
||||||
proc findLatestJustifiedBlock(
|
let hlen = pool.heads.len
|
||||||
blck: BlockRef, depth: int, deepest: var tuple[depth: int, blck: BlockRef]) =
|
for i in 0..<hlen:
|
||||||
if blck.justified and depth > deepest.depth:
|
let n = hlen - i - 1
|
||||||
deepest = (depth, blck)
|
if pool.heads[n].blck.slot < pool.finalizedHead.blck.slot and
|
||||||
|
not pool.heads[n].blck.isAncestorOf(pool.finalizedHead.blck):
|
||||||
for child in blck.children:
|
pool.heads.del(n)
|
||||||
findLatestJustifiedBlock(child, depth + 1, deepest)
|
|
||||||
|
|
||||||
proc latestJustifiedBlock*(pool: BlockPool): BlockRef =
|
proc latestJustifiedBlock*(pool: BlockPool): BlockRef =
|
||||||
## Return the most recent block that is justified and at least as recent
|
## Return the most recent block that is justified and at least as recent
|
||||||
## as the latest finalized block
|
## as the latest finalized block
|
||||||
|
|
||||||
var deepest = (0, pool.finalizedHead)
|
doAssert pool.heads.len > 0,
|
||||||
|
"We should have at least the genesis block in heaads"
|
||||||
findLatestJustifiedBlock(pool.finalizedHead, 0, deepest)
|
doAssert (not pool.head.blck.isNil()),
|
||||||
|
"Genesis block will be head, if nothing else"
|
||||||
deepest[1]
|
|
||||||
|
|
||||||
proc latestState*(pool: BlockPool): BeaconState =
|
|
||||||
var b = pool.head
|
|
||||||
while true:
|
|
||||||
if b.isNil:
|
|
||||||
raise newException(Exception, "No state found")
|
|
||||||
if (let blk = pool.db.getBlock(b.root); blk.isSome()):
|
|
||||||
if (let state = pool.db.getState(blk.get().stateRoot); state.isSome()):
|
|
||||||
return state.get()
|
|
||||||
else:
|
|
||||||
error "Block from block pool not found in db", root = b.root
|
|
||||||
b = b.parent
|
|
||||||
|
|
||||||
|
# Prefer stability: use justified block from current head to break ties!
|
||||||
|
result = pool.head.justified.blck
|
||||||
|
for head in pool.heads[1 ..< ^0]:
|
||||||
|
if head.justified.blck.slot > result.slot:
|
||||||
|
result = head.justified.blck
|
||||||
|
|
||||||
proc preInit*(
|
proc preInit*(
|
||||||
T: type BlockPool, db: BeaconChainDB, state: BeaconState, blck: BeaconBlock) =
|
T: type BlockPool, db: BeaconChainDB, state: BeaconState, blck: BeaconBlock) =
|
||||||
|
|
|
@ -13,8 +13,9 @@ func ceil_div8(v: int): int = (v + 7) div 8
|
||||||
func init*(T: type BitField, bits: int): BitField =
|
func init*(T: type BitField, bits: int): BitField =
|
||||||
BitField(bits: newSeq[byte](ceil_div8(bits)))
|
BitField(bits: newSeq[byte](ceil_div8(bits)))
|
||||||
|
|
||||||
proc readValue*(r: var JsonReader, a: var BitField) {.inline.} =
|
# TODO fix this for state tests..
|
||||||
a.bits = r.readValue(string).hexToSeqByte()
|
#proc readValue*(r: var JsonReader, a: var BitField) {.inline.} =
|
||||||
|
# a.bits = r.readValue(string).hexToSeqByte()
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.0/specs/core/0_beacon-chain.md#get_bitfield_bit
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.0/specs/core/0_beacon-chain.md#get_bitfield_bit
|
||||||
func get_bitfield_bit*(bitfield: BitField, i: int): bool =
|
func get_bitfield_bit*(bitfield: BitField, i: int): bool =
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import
|
import
|
||||||
options, tables,
|
options, tables,
|
||||||
chronicles, chronos, ranges/bitranges,
|
chronicles, chronos, ranges/bitranges,
|
||||||
spec/[datatypes, crypto, digest], eth/rlp,
|
spec/[datatypes, crypto, digest, helpers], eth/rlp,
|
||||||
beacon_node_types, eth2_network, beacon_chain_db, block_pool, time, ssz
|
beacon_node_types, eth2_network, beacon_chain_db, block_pool, time, ssz
|
||||||
|
|
||||||
from beacon_node import onBeaconBlock
|
from beacon_node import onBeaconBlock
|
||||||
|
@ -81,17 +81,14 @@ p2pProtocol BeaconSync(version = 1,
|
||||||
node = peer.networkState.node
|
node = peer.networkState.node
|
||||||
networkId = peer.networkState.networkId
|
networkId = peer.networkState.networkId
|
||||||
blockPool = node.blockPool
|
blockPool = node.blockPool
|
||||||
latestState = blockPool.latestState()
|
finalizedHead = blockPool.finalizedHead
|
||||||
headBlock = blockPool.head
|
headBlock = blockPool.head.blck
|
||||||
|
bestRoot = headBlock.root
|
||||||
var
|
|
||||||
latestFinalizedRoot: Eth2Digest # TODO
|
|
||||||
latestFinalizedEpoch = latestState.finalized_epoch
|
|
||||||
bestRoot: Eth2Digest # TODO
|
|
||||||
bestSlot = headBlock.slot
|
bestSlot = headBlock.slot
|
||||||
|
latestFinalizedEpoch = finalizedHead.slot.slot_to_epoch()
|
||||||
|
|
||||||
let m = await handshake(peer, timeout = 10.seconds,
|
let m = await handshake(peer, timeout = 10.seconds,
|
||||||
status(networkId, latestFinalizedRoot,
|
status(networkId, finalizedHead.blck.root,
|
||||||
latestFinalizedEpoch, bestRoot, bestSlot))
|
latestFinalizedEpoch, bestRoot, bestSlot))
|
||||||
|
|
||||||
if m.networkId != networkId:
|
if m.networkId != networkId:
|
||||||
|
@ -169,7 +166,7 @@ p2pProtocol BeaconSync(version = 1,
|
||||||
var s = fromSlot
|
var s = fromSlot
|
||||||
var roots = newSeqOfCap[(Eth2Digest, Slot)](maxRoots)
|
var roots = newSeqOfCap[(Eth2Digest, Slot)](maxRoots)
|
||||||
let blockPool = peer.networkState.node.blockPool
|
let blockPool = peer.networkState.node.blockPool
|
||||||
let maxSlot = blockPool.head.slot
|
let maxSlot = blockPool.head.blck.slot
|
||||||
while s <= maxSlot:
|
while s <= maxSlot:
|
||||||
for r in blockPool.blockRootsForSlot(s):
|
for r in blockPool.blockRootsForSlot(s):
|
||||||
roots.add((r, s))
|
roots.add((r, s))
|
||||||
|
@ -192,7 +189,7 @@ p2pProtocol BeaconSync(version = 1,
|
||||||
var headers = newSeqOfCap[BeaconBlockHeaderRLP](maxHeaders)
|
var headers = newSeqOfCap[BeaconBlockHeaderRLP](maxHeaders)
|
||||||
let db = peer.networkState.db
|
let db = peer.networkState.db
|
||||||
let blockPool = peer.networkState.node.blockPool
|
let blockPool = peer.networkState.node.blockPool
|
||||||
let maxSlot = blockPool.head.slot
|
let maxSlot = blockPool.head.blck.slot
|
||||||
while s <= maxSlot:
|
while s <= maxSlot:
|
||||||
for r in blockPool.blockRootsForSlot(s):
|
for r in blockPool.blockRootsForSlot(s):
|
||||||
headers.add(db.getBlock(r).get().toHeader)
|
headers.add(db.getBlock(r).get().toHeader)
|
||||||
|
|
Loading…
Reference in New Issue