mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-10 22:36:01 +00:00
Fork choice update for HF1
This commit is contained in:
parent
3d25f0db01
commit
597374605a
@ -97,7 +97,7 @@ proc compute_slots_since_epoch_start(slot: Slot): uint64 =
|
|||||||
|
|
||||||
proc on_tick(self: var Checkpoints, dag: ChainDAGRef, time: Slot): FcResult[void] =
|
proc on_tick(self: var Checkpoints, dag: ChainDAGRef, time: Slot): FcResult[void] =
|
||||||
if self.time > time:
|
if self.time > time:
|
||||||
return err(ForkChoiceError(kind: fcInconsistentTick))
|
return err ForkChoiceError(kind: fcInconsistentTick)
|
||||||
|
|
||||||
let newEpoch = self.time.epoch() != time.epoch()
|
let newEpoch = self.time.epoch() != time.epoch()
|
||||||
self.time = time
|
self.time = time
|
||||||
@ -106,8 +106,9 @@ proc on_tick(self: var Checkpoints, dag: ChainDAGRef, time: Slot): FcResult[void
|
|||||||
self.best_justified.epoch > self.justified.epoch:
|
self.best_justified.epoch > self.justified.epoch:
|
||||||
let blck = dag.getRef(self.best_justified.root)
|
let blck = dag.getRef(self.best_justified.root)
|
||||||
if blck.isNil:
|
if blck.isNil:
|
||||||
return err(ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcJustifiedNodeUnknown, block_root: self.best_justified.root))
|
kind: fcJustifiedNodeUnknown,
|
||||||
|
blockRoot: self.best_justified.root)
|
||||||
|
|
||||||
let epochRef = dag.getEpochRef(blck, self.best_justified.epoch)
|
let epochRef = dag.getEpochRef(blck, self.best_justified.epoch)
|
||||||
self.justified = BalanceCheckpoint(
|
self.justified = BalanceCheckpoint(
|
||||||
@ -117,6 +118,7 @@ proc on_tick(self: var Checkpoints, dag: ChainDAGRef, time: Slot): FcResult[void
|
|||||||
ok()
|
ok()
|
||||||
|
|
||||||
proc process_attestation_queue(self: var ForkChoice) {.gcsafe.}
|
proc process_attestation_queue(self: var ForkChoice) {.gcsafe.}
|
||||||
|
|
||||||
proc update_time(self: var ForkChoice, dag: ChainDAGRef, time: Slot): FcResult[void] =
|
proc update_time(self: var ForkChoice, dag: ChainDAGRef, time: Slot): FcResult[void] =
|
||||||
if time > self.checkpoints.time:
|
if time > self.checkpoints.time:
|
||||||
while time > self.checkpoints.time:
|
while time > self.checkpoints.time:
|
||||||
@ -209,8 +211,9 @@ proc should_update_justified_checkpoint(
|
|||||||
justified_blck = dag.getRef(new_justified_checkpoint.root)
|
justified_blck = dag.getRef(new_justified_checkpoint.root)
|
||||||
|
|
||||||
if justified_blck.isNil:
|
if justified_blck.isNil:
|
||||||
return err(ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcJustifiedNodeUnknown, block_root: new_justified_checkpoint.root))
|
kind: fcJustifiedNodeUnknown,
|
||||||
|
blockRoot: new_justified_checkpoint.root)
|
||||||
|
|
||||||
let justified_ancestor = justified_blck.atSlot(justified_slot)
|
let justified_ancestor = justified_blck.atSlot(justified_slot)
|
||||||
|
|
||||||
@ -275,7 +278,7 @@ proc process_block*(self: var ForkChoiceBackend,
|
|||||||
parent_root: Eth2Digest,
|
parent_root: Eth2Digest,
|
||||||
justified_epoch: Epoch,
|
justified_epoch: Epoch,
|
||||||
finalized_epoch: Epoch): FcResult[void] =
|
finalized_epoch: Epoch): FcResult[void] =
|
||||||
self.proto_array.on_block(
|
self.proto_array.onBlock(
|
||||||
block_root, parent_root, justified_epoch, finalized_epoch)
|
block_root, parent_root, justified_epoch, finalized_epoch)
|
||||||
|
|
||||||
proc process_block*(self: var ForkChoice,
|
proc process_block*(self: var ForkChoice,
|
||||||
@ -336,7 +339,7 @@ proc find_head*(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Apply score changes
|
# Apply score changes
|
||||||
? self.proto_array.apply_score_changes(
|
? self.proto_array.applyScoreChanges(
|
||||||
deltas, justified_epoch, finalized_epoch
|
deltas, justified_epoch, finalized_epoch
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -344,7 +347,7 @@ proc find_head*(
|
|||||||
|
|
||||||
# Find the best block
|
# Find the best block
|
||||||
var new_head{.noInit.}: Eth2Digest
|
var new_head{.noInit.}: Eth2Digest
|
||||||
? self.proto_array.find_head(new_head, justified_root)
|
? self.proto_array.findHead(new_head, justified_root)
|
||||||
|
|
||||||
{.noSideEffect.}:
|
{.noSideEffect.}:
|
||||||
trace "Fork choice requested",
|
trace "Fork choice requested",
|
||||||
@ -425,8 +428,7 @@ func compute_deltas(
|
|||||||
if index >= deltas.len:
|
if index >= deltas.len:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidNodeDelta,
|
kind: fcInvalidNodeDelta,
|
||||||
index: index
|
index: index)
|
||||||
)
|
|
||||||
deltas[index] -= Delta old_balance
|
deltas[index] -= Delta old_balance
|
||||||
# Note that delta can be negative
|
# Note that delta can be negative
|
||||||
# TODO: is int64 big enough?
|
# TODO: is int64 big enough?
|
||||||
@ -436,8 +438,7 @@ func compute_deltas(
|
|||||||
if index >= deltas.len:
|
if index >= deltas.len:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidNodeDelta,
|
kind: fcInvalidNodeDelta,
|
||||||
index: index
|
index: index)
|
||||||
)
|
|
||||||
deltas[index] += Delta new_balance
|
deltas[index] += Delta new_balance
|
||||||
# Note that delta can be negative
|
# Note that delta can be negative
|
||||||
# TODO: is int64 big enough?
|
# TODO: is int64 big enough?
|
||||||
|
@ -76,7 +76,7 @@ type
|
|||||||
case kind*: fcKind
|
case kind*: fcKind
|
||||||
of fcFinalizedNodeUnknown,
|
of fcFinalizedNodeUnknown,
|
||||||
fcJustifiedNodeUnknown:
|
fcJustifiedNodeUnknown:
|
||||||
block_root*: Eth2Digest
|
blockRoot*: Eth2Digest
|
||||||
of fcInvalidFinalizedRootChange,
|
of fcInvalidFinalizedRootChange,
|
||||||
fcInconsistentTick:
|
fcInconsistentTick:
|
||||||
discard
|
discard
|
||||||
@ -95,18 +95,18 @@ type
|
|||||||
deltasLen*: int
|
deltasLen*: int
|
||||||
indicesLen*: int
|
indicesLen*: int
|
||||||
of fcRevertedFinalizedEpoch:
|
of fcRevertedFinalizedEpoch:
|
||||||
current_finalized_epoch*: Epoch
|
currentFinalizedEpoch*: Epoch
|
||||||
new_finalized_epoch*: Epoch
|
new_finalized_epoch*: Epoch
|
||||||
of fcInvalidBestNode:
|
of fcInvalidBestNode:
|
||||||
start_root*: Eth2Digest
|
startRoot*: Eth2Digest
|
||||||
justified_epoch*: Epoch
|
justifiedEpoch*: Epoch
|
||||||
finalized_epoch*: Epoch
|
finalizedEpoch*: Epoch
|
||||||
head_root*: Eth2Digest
|
headRoot*: Eth2Digest
|
||||||
head_justified_epoch*: Epoch
|
headJustifiedEpoch*: Epoch
|
||||||
head_finalized_epoch*: Epoch
|
headFinalizedEpoch*: Epoch
|
||||||
of fcUnknownParent:
|
of fcUnknownParent:
|
||||||
child_root*: Eth2Digest
|
childRoot*: Eth2Digest
|
||||||
parent_root*: Eth2Digest
|
parentRoot*: Eth2Digest
|
||||||
of fcPruningFromOutdatedFinalizedRoot:
|
of fcPruningFromOutdatedFinalizedRoot:
|
||||||
finalizedRoot*: Eth2Digest
|
finalizedRoot*: Eth2Digest
|
||||||
|
|
||||||
@ -119,19 +119,19 @@ type
|
|||||||
## to get the physical index
|
## to get the physical index
|
||||||
|
|
||||||
ProtoArray* = object
|
ProtoArray* = object
|
||||||
justified_epoch*: Epoch
|
justifiedEpoch*: Epoch
|
||||||
finalized_epoch*: Epoch
|
finalizedEpoch*: Epoch
|
||||||
nodes*: Protonodes
|
nodes*: Protonodes
|
||||||
indices*: Table[Eth2Digest, Index]
|
indices*: Table[Eth2Digest, Index]
|
||||||
|
|
||||||
ProtoNode* = object
|
ProtoNode* = object
|
||||||
root*: Eth2Digest
|
root*: Eth2Digest
|
||||||
parent*: Option[Index]
|
parent*: Option[Index]
|
||||||
justified_epoch*: Epoch
|
justifiedEpoch*: Epoch
|
||||||
finalized_epoch*: Epoch
|
finalizedEpoch*: Epoch
|
||||||
weight*: int64
|
weight*: int64
|
||||||
best_child*: Option[Index]
|
bestChild*: Option[Index]
|
||||||
best_descendant*: Option[Index]
|
bestDescendant*: Option[Index]
|
||||||
|
|
||||||
BalanceCheckpoint* = object
|
BalanceCheckpoint* = object
|
||||||
blck*: BlockRef
|
blck*: BlockRef
|
||||||
|
@ -73,41 +73,41 @@ func add(nodes: var ProtoNodes, node: ProtoNode) =
|
|||||||
# Forward declarations
|
# Forward declarations
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
func maybe_update_best_child_and_descendant(
|
func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
|
||||||
self: var ProtoArray, parent_index: Index, child_index: Index): FcResult[void]
|
parentIdx: Index,
|
||||||
func node_is_viable_for_head(self: ProtoArray, node: ProtoNode): bool
|
childIdx: Index): FcResult[void]
|
||||||
func node_leads_to_viable_head(self: ProtoArray, node: ProtoNode): FcResult[bool]
|
|
||||||
|
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool
|
||||||
|
func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool]
|
||||||
|
|
||||||
# ProtoArray routines
|
# ProtoArray routines
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
func init*(T: type ProtoArray,
|
func init*(T: type ProtoArray,
|
||||||
justified_epoch: Epoch,
|
justifiedEpoch: Epoch,
|
||||||
finalized_root: Eth2Digest,
|
finalizedRoot: Eth2Digest,
|
||||||
finalized_epoch: Epoch): T =
|
finalizedEpoch: Epoch): T =
|
||||||
let node = ProtoNode(
|
let node = ProtoNode(
|
||||||
root: finalized_root,
|
root: finalizedRoot,
|
||||||
parent: none(int),
|
parent: none(int),
|
||||||
justified_epoch: justified_epoch,
|
justifiedEpoch: justifiedEpoch,
|
||||||
finalized_epoch: finalized_epoch,
|
finalizedEpoch: finalizedEpoch,
|
||||||
weight: 0,
|
weight: 0,
|
||||||
best_child: none(int),
|
bestChild: none(int),
|
||||||
best_descendant: none(int)
|
bestDescendant: none(int)
|
||||||
)
|
)
|
||||||
|
|
||||||
T(
|
T(
|
||||||
justified_epoch: justified_epoch,
|
justifiedEpoch: justifiedEpoch,
|
||||||
finalized_epoch: finalized_epoch,
|
finalizedEpoch: finalizedEpoch,
|
||||||
nodes: ProtoNodes(buf: @[node], offset: 0),
|
nodes: ProtoNodes(buf: @[node], offset: 0),
|
||||||
indices: {node.root: 0}.toTable()
|
indices: {node.root: 0}.toTable()
|
||||||
)
|
)
|
||||||
|
|
||||||
func apply_score_changes*(
|
func applyScoreChanges*(self: var ProtoArray,
|
||||||
self: var ProtoArray,
|
deltas: var openArray[Delta],
|
||||||
deltas: var openArray[Delta],
|
justifiedEpoch: Epoch,
|
||||||
justified_epoch: Epoch,
|
finalizedEpoch: Epoch): FcResult[void] =
|
||||||
finalized_epoch: Epoch
|
|
||||||
): FcResult[void] =
|
|
||||||
## Iterate backwards through the array, touching all nodes and their parents
|
## Iterate backwards through the array, touching all nodes and their parents
|
||||||
## and potentially the best-child of each parent.
|
## and potentially the best-child of each parent.
|
||||||
##
|
##
|
||||||
@ -124,41 +124,40 @@ func apply_score_changes*(
|
|||||||
doAssert self.indices.len == self.nodes.len # By construction
|
doAssert self.indices.len == self.nodes.len # By construction
|
||||||
if deltas.len != self.indices.len:
|
if deltas.len != self.indices.len:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidDeltaLen,
|
kind: fcInvalidDeltaLen,
|
||||||
deltasLen: deltas.len,
|
deltasLen: deltas.len,
|
||||||
indicesLen: self.indices.len
|
indicesLen: self.indices.len)
|
||||||
)
|
|
||||||
|
|
||||||
self.justified_epoch = justified_epoch
|
self.justifiedEpoch = justifiedEpoch
|
||||||
self.finalized_epoch = finalized_epoch
|
self.finalizedEpoch = finalizedEpoch
|
||||||
|
|
||||||
|
## Alias
|
||||||
|
# This cannot raise the IndexError exception, how to tell compiler?
|
||||||
|
template node: untyped {.dirty.} =
|
||||||
|
self.nodes.buf[nodePhysicalIdx]
|
||||||
|
|
||||||
# Iterate backwards through all the indices in `self.nodes`
|
# Iterate backwards through all the indices in `self.nodes`
|
||||||
for node_physical_index in countdown(self.nodes.len - 1, 0):
|
for nodePhysicalIdx in countdown(self.nodes.len - 1, 0):
|
||||||
template node: untyped {.dirty.}= self.nodes.buf[node_physical_index]
|
|
||||||
## Alias
|
|
||||||
# This cannot raise the IndexError exception, how to tell compiler?
|
|
||||||
|
|
||||||
if node.root == default(Eth2Digest):
|
if node.root == default(Eth2Digest):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
let node_delta = deltas[node_physical_index]
|
let nodeDelta = deltas[nodePhysicalIdx]
|
||||||
|
|
||||||
# Apply the delta to the node
|
# Apply the delta to the node
|
||||||
# We fail fast if underflow, which shouldn't happen.
|
# We fail fast if underflow, which shouldn't happen.
|
||||||
# Note that delta can be negative but weight cannot
|
# Note that delta can be negative but weight cannot
|
||||||
let weight = node.weight + node_delta
|
let weight = node.weight + nodeDelta
|
||||||
if weight < 0:
|
if weight < 0:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcDeltaUnderflow,
|
kind: fcDeltaUnderflow,
|
||||||
index: node_physical_index
|
index: nodePhysicalIdx)
|
||||||
)
|
|
||||||
node.weight = weight
|
node.weight = weight
|
||||||
|
|
||||||
# If the node has a parent, try to update its best-child and best-descendant
|
# If the node has a parent, try to update its best-child and best-descendant
|
||||||
if node.parent.isSome():
|
if node.parent.isSome():
|
||||||
let parent_logical_index = node.parent.unsafeGet()
|
let parentLogicalIdx = node.parent.unsafeGet()
|
||||||
let parent_physical_index = parent_logical_index - self.nodes.offset
|
let parentPhysicalIdx = parentLogicalIdx - self.nodes.offset
|
||||||
if parent_physical_index < 0:
|
if parentPhysicalIdx < 0:
|
||||||
# Orphan, for example
|
# Orphan, for example
|
||||||
# 0
|
# 0
|
||||||
# / \
|
# / \
|
||||||
@ -180,27 +179,35 @@ func apply_score_changes*(
|
|||||||
# that will be pruned next.
|
# that will be pruned next.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if parent_physical_index >= deltas.len:
|
if parentPhysicalIdx >= deltas.len:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidParentDelta,
|
kind: fcInvalidParentDelta,
|
||||||
index: parent_physical_index
|
index: parentPhysicalIdx)
|
||||||
)
|
|
||||||
|
|
||||||
# Back-propagate the nodes delta to its parent.
|
# Back-propagate the nodes delta to its parent.
|
||||||
deltas[parent_physical_index] += node_delta
|
deltas[parentPhysicalIdx] += nodeDelta
|
||||||
|
|
||||||
let node_logical_index = node_physical_index + self.nodes.offset
|
for nodePhysicalIdx in countdown(self.nodes.len - 1, 0):
|
||||||
? self.maybe_update_best_child_and_descendant(parent_logical_index, node_logical_index)
|
if node.root == default(Eth2Digest):
|
||||||
|
continue
|
||||||
|
|
||||||
return ok()
|
if node.parent.isSome():
|
||||||
|
let parentLogicalIdx = node.parent.unsafeGet()
|
||||||
|
let parentPhysicalIdx = parentLogicalIdx - self.nodes.offset
|
||||||
|
if parentPhysicalIdx < 0:
|
||||||
|
# Orphan
|
||||||
|
continue
|
||||||
|
|
||||||
func on_block*(
|
let nodeLogicalIdx = nodePhysicalIdx + self.nodes.offset
|
||||||
self: var ProtoArray,
|
? self.maybeUpdateBestChildAndDescendant(parentLogicalIdx, nodeLogicalIdx)
|
||||||
root: Eth2Digest,
|
|
||||||
parent: Eth2Digest,
|
ok()
|
||||||
justified_epoch: Epoch,
|
|
||||||
finalized_epoch: Epoch
|
func onBlock*(self: var ProtoArray,
|
||||||
): FcResult[void] =
|
root: Eth2Digest,
|
||||||
|
parent: Eth2Digest,
|
||||||
|
justifiedEpoch: Epoch,
|
||||||
|
finalizedEpoch: Epoch): FcResult[void] =
|
||||||
## Register a block with the fork choice
|
## Register a block with the fork choice
|
||||||
## A block `hasParentInForkChoice` may be false
|
## A block `hasParentInForkChoice` may be false
|
||||||
## on fork choice initialization:
|
## on fork choice initialization:
|
||||||
@ -213,145 +220,130 @@ func on_block*(
|
|||||||
if root in self.indices:
|
if root in self.indices:
|
||||||
return ok()
|
return ok()
|
||||||
|
|
||||||
var parent_index: Index
|
var parentIdx: Index
|
||||||
self.indices.withValue(parent, index) do:
|
self.indices.withValue(parent, index) do:
|
||||||
parent_index = index[]
|
parentIdx = index[]
|
||||||
do:
|
do:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcUnknownParent,
|
kind: fcUnknownParent,
|
||||||
child_root: root,
|
childRoot: root,
|
||||||
parent_root: parent
|
parentRoot: parent)
|
||||||
)
|
|
||||||
|
|
||||||
let node_logical_index = self.nodes.offset + self.nodes.buf.len
|
let nodeLogicalIdx = self.nodes.offset + self.nodes.buf.len
|
||||||
|
|
||||||
let node = ProtoNode(
|
let node = ProtoNode(
|
||||||
root: root,
|
root: root,
|
||||||
parent: some(parent_index),
|
parent: some(parentIdx),
|
||||||
justified_epoch: justified_epoch,
|
justifiedEpoch: justifiedEpoch,
|
||||||
finalized_epoch: finalized_epoch,
|
finalizedEpoch: finalizedEpoch,
|
||||||
weight: 0,
|
weight: 0,
|
||||||
best_child: none(int),
|
bestChild: none(int),
|
||||||
best_descendant: none(int)
|
bestDescendant: none(int)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.indices[node.root] = node_logical_index
|
self.indices[node.root] = nodeLogicalIdx
|
||||||
self.nodes.add node
|
self.nodes.add node
|
||||||
|
|
||||||
? self.maybe_update_best_child_and_descendant(parent_index, node_logical_index)
|
? self.maybeUpdateBestChildAndDescendant(parentIdx, nodeLogicalIdx)
|
||||||
|
|
||||||
return ok()
|
ok()
|
||||||
|
|
||||||
func find_head*(
|
func findHead*(self: var ProtoArray,
|
||||||
self: var ProtoArray,
|
head: var Eth2Digest,
|
||||||
head: var Eth2Digest,
|
justifiedRoot: Eth2Digest): FcResult[void] =
|
||||||
justified_root: Eth2Digest
|
|
||||||
): FcResult[void] =
|
|
||||||
## Follows the best-descendant links to find the best-block (i.e. head-block)
|
## Follows the best-descendant links to find the best-block (i.e. head-block)
|
||||||
##
|
##
|
||||||
## ⚠️ Warning
|
## ⚠️ Warning
|
||||||
## The result may not be accurate if `on_new_block`
|
## The result may not be accurate if `onBlock` is not followed by
|
||||||
## is not followed by `apply_score_changes` as `on_new_block` does not
|
## `applyScoreChanges` as `onBlock` does not update the whole tree.
|
||||||
## update the whole tree.
|
|
||||||
|
|
||||||
var justified_index: Index
|
var justifiedIdx: Index
|
||||||
self.indices.withValue(justified_root, value) do:
|
self.indices.withValue(justifiedRoot, value) do:
|
||||||
justified_index = value[]
|
justifiedIdx = value[]
|
||||||
do:
|
do:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcJustifiedNodeUnknown,
|
kind: fcJustifiedNodeUnknown,
|
||||||
block_root: justified_root
|
blockRoot: justifiedRoot)
|
||||||
)
|
|
||||||
|
|
||||||
let justified_node = self.nodes[justified_index]
|
let justifiedNode = self.nodes[justifiedIdx]
|
||||||
if justified_node.isNone():
|
if justifiedNode.isNone():
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidJustifiedIndex,
|
kind: fcInvalidJustifiedIndex,
|
||||||
index: justified_index
|
index: justifiedIdx)
|
||||||
)
|
|
||||||
|
|
||||||
let best_descendant_index = justified_node.get().best_descendant.get(justified_index)
|
let bestDescendantIdx = justifiedNode.get().bestDescendant.get(justifiedIdx)
|
||||||
let best_node = self.nodes[best_descendant_index]
|
let bestNode = self.nodes[bestDescendantIdx]
|
||||||
if best_node.isNone():
|
if bestNode.isNone():
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidBestDescendant,
|
kind: fcInvalidBestDescendant,
|
||||||
index: best_descendant_index
|
index: bestDescendantIdx)
|
||||||
)
|
|
||||||
|
|
||||||
# Perform a sanity check to ensure the node can be head
|
# Perform a sanity check to ensure the node can be head
|
||||||
if not self.node_is_viable_for_head(best_node.get()):
|
if not self.nodeIsViableForHead(bestNode.get()):
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidBestNode,
|
kind: fcInvalidBestNode,
|
||||||
start_root: justified_root,
|
startRoot: justifiedRoot,
|
||||||
justified_epoch: self.justified_epoch,
|
justifiedEpoch: self.justifiedEpoch,
|
||||||
finalized_epoch: self.finalized_epoch,
|
finalizedEpoch: self.finalizedEpoch,
|
||||||
head_root: justified_node.get().root,
|
headRoot: justifiedNode.get().root,
|
||||||
head_justified_epoch: justified_node.get().justified_epoch,
|
headJustifiedEpoch: justifiedNode.get().justifiedEpoch,
|
||||||
head_finalized_epoch: justified_node.get().finalized_epoch
|
headFinalizedEpoch: justifiedNode.get().finalizedEpoch)
|
||||||
)
|
|
||||||
|
|
||||||
head = best_node.get().root
|
head = bestNode.get().root
|
||||||
return ok()
|
ok()
|
||||||
|
|
||||||
func prune*(
|
func prune*(self: var ProtoArray, finalizedRoot: Eth2Digest): FcResult[void] =
|
||||||
self: var ProtoArray,
|
|
||||||
finalized_root: Eth2Digest
|
|
||||||
): FcResult[void] =
|
|
||||||
## Update the tree with new finalization information.
|
## Update the tree with new finalization information.
|
||||||
## The tree is pruned if and only if:
|
## The tree is pruned if and only if:
|
||||||
## - The `finalized_root` and finalized epoch are different from current
|
## - The `finalizedRoot` and finalized epoch are different from current
|
||||||
##
|
##
|
||||||
## Returns error if:
|
## Returns error if:
|
||||||
## - The finalized epoch is less than the current one
|
## - The finalized epoch is less than the current one
|
||||||
## - The finalized epoch matches the current one but the finalized root is different
|
## - The finalized epoch matches the current one but the finalized root is different
|
||||||
## - Internal error due to invalid indices in `self`
|
## - Internal error due to invalid indices in `self`
|
||||||
|
|
||||||
var finalized_index: int
|
var finalizedIdx: int
|
||||||
self.indices.withValue(finalized_root, value) do:
|
self.indices.withValue(finalizedRoot, value) do:
|
||||||
finalized_index = value[]
|
finalizedIdx = value[]
|
||||||
do:
|
do:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcFinalizedNodeUnknown,
|
kind: fcFinalizedNodeUnknown,
|
||||||
block_root: finalized_root
|
blockRoot: finalizedRoot)
|
||||||
)
|
|
||||||
|
|
||||||
if finalized_index == self.nodes.offset:
|
if finalizedIdx == self.nodes.offset:
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
return ok()
|
return ok()
|
||||||
|
|
||||||
if finalized_index < self.nodes.offset:
|
if finalizedIdx < self.nodes.offset:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcPruningFromOutdatedFinalizedRoot,
|
kind: fcPruningFromOutdatedFinalizedRoot,
|
||||||
finalizedRoot: finalized_root
|
finalizedRoot: finalizedRoot)
|
||||||
)
|
|
||||||
|
|
||||||
trace "Pruning blocks from fork choice",
|
trace "Pruning blocks from fork choice",
|
||||||
finalizedRoot = shortlog(finalized_root)
|
finalizedRoot = shortlog(finalizedRoot)
|
||||||
|
|
||||||
let final_phys_index = finalized_index-self.nodes.offset
|
let finalPhysicalIdx = finalizedIdx - self.nodes.offset
|
||||||
for node_index in 0 ..< final_phys_index:
|
for nodeIdx in 0 ..< finalPhysicalIdx:
|
||||||
self.indices.del(self.nodes.buf[node_index].root)
|
self.indices.del(self.nodes.buf[nodeIdx].root)
|
||||||
|
|
||||||
# Drop all nodes prior to finalization.
|
# Drop all nodes prior to finalization.
|
||||||
# This is done in-place with `moveMem` to avoid costly reallocations.
|
# This is done in-place with `moveMem` to avoid costly reallocations.
|
||||||
static: doAssert ProtoNode.supportsCopyMem(), "ProtoNode must be a trivial type"
|
static: doAssert ProtoNode.supportsCopyMem(), "ProtoNode must be a trivial type"
|
||||||
let tail = self.nodes.len - final_phys_index
|
let tail = self.nodes.len - finalPhysicalIdx
|
||||||
# TODO: can we have an unallocated `self.nodes`? i.e. self.nodes[0] is nil
|
# TODO: can we have an unallocated `self.nodes`? i.e. self.nodes[0] is nil
|
||||||
moveMem(self.nodes.buf[0].addr, self.nodes.buf[final_phys_index].addr, tail * sizeof(ProtoNode))
|
moveMem(self.nodes.buf[0].addr, self.nodes.buf[finalPhysicalIdx].addr, tail * sizeof(ProtoNode))
|
||||||
self.nodes.buf.setLen(tail)
|
self.nodes.buf.setLen(tail)
|
||||||
|
|
||||||
# update offset
|
# update offset
|
||||||
self.nodes.offset = finalized_index
|
self.nodes.offset = finalizedIdx
|
||||||
|
|
||||||
return ok()
|
ok()
|
||||||
|
|
||||||
|
func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
|
||||||
func maybe_update_best_child_and_descendant(
|
parentIdx: Index,
|
||||||
self: var ProtoArray,
|
childIdx: Index): FcResult[void] =
|
||||||
parent_index: Index,
|
## Observe the parent at `parentIdx` with respect to the child at `childIdx` and
|
||||||
child_index: Index): Result[void, ForkChoiceError] =
|
## potentially modify the `parent.bestChild` and `parent.bestDescendant` values
|
||||||
## Observe the parent at `parent_index` with respect to the child at `child_index` and
|
|
||||||
## potentially modify the `parent.best_child` and `parent.best_descendant` values
|
|
||||||
##
|
##
|
||||||
## There are four scenarios:
|
## There are four scenarios:
|
||||||
##
|
##
|
||||||
@ -362,120 +354,113 @@ func maybe_update_best_child_and_descendant(
|
|||||||
## 3. The child is not the best child but becomes the best child
|
## 3. The child is not the best child but becomes the best child
|
||||||
## 4. The child is not the best child and does not become the best child
|
## 4. The child is not the best child and does not become the best child
|
||||||
|
|
||||||
let child = self.nodes[child_index]
|
let child = self.nodes[childIdx]
|
||||||
if child.isNone():
|
if child.isNone():
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidNodeIndex,
|
kind: fcInvalidNodeIndex,
|
||||||
index: child_index
|
index: childIdx)
|
||||||
)
|
|
||||||
|
|
||||||
let parent = self.nodes[parent_index]
|
let parent = self.nodes[parentIdx]
|
||||||
if parent.isNone():
|
if parent.isNone():
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidNodeIndex,
|
kind: fcInvalidNodeIndex,
|
||||||
index: parent_index
|
index: parentIdx)
|
||||||
)
|
|
||||||
|
|
||||||
let child_leads_to_viable_head = ? self.node_leads_to_viable_head(child.get())
|
let childLeadsToViableHead = ? self.nodeLeadsToViableHead(child.get())
|
||||||
|
|
||||||
let # Aliases to the 3 possible (best_child, best_descendant) tuples
|
let # Aliases to the 3 possible (bestChild, bestDescendant) tuples
|
||||||
change_to_none = (none(Index), none(Index))
|
changeToNone = (none(Index), none(Index))
|
||||||
change_to_child = (
|
changeToChild = (
|
||||||
some(child_index),
|
some(childIdx),
|
||||||
# Nim `options` module doesn't implement option `or`
|
# Nim `options` module doesn't implement option `or`
|
||||||
if child.get().best_descendant.isSome(): child.get().best_descendant
|
if child.get().bestDescendant.isSome(): child.get().bestDescendant
|
||||||
else: some(child_index)
|
else: some(childIdx)
|
||||||
)
|
)
|
||||||
no_change = (parent.get().best_child, parent.get().best_descendant)
|
noChange = (parent.get().bestChild, parent.get().bestDescendant)
|
||||||
|
|
||||||
# TODO: state-machine? The control-flow is messy
|
# TODO: state-machine? The control-flow is messy
|
||||||
let (new_best_child, new_best_descendant) = block:
|
let (newBestChild, newBestDescendant) = block:
|
||||||
if parent.get().best_child.isSome:
|
if parent.get().bestChild.isSome:
|
||||||
let best_child_index = parent.get().best_child.unsafeGet()
|
let bestChildIdx = parent.get().bestChild.unsafeGet()
|
||||||
if best_child_index == child_index and not child_leads_to_viable_head:
|
if bestChildIdx == childIdx and not childLeadsToViableHead:
|
||||||
# The child is already the best-child of the parent
|
# The child is already the best-child of the parent
|
||||||
# but it's not viable to be the head block => remove it
|
# but it's not viable to be the head block => remove it
|
||||||
change_to_none
|
changeToNone
|
||||||
elif best_child_index == child_index:
|
elif bestChildIdx == childIdx:
|
||||||
# If the child is the best-child already, set it again to ensure
|
# If the child is the best-child already, set it again to ensure
|
||||||
# that the best-descendant of the parent is up-to-date.
|
# that the best-descendant of the parent is up-to-date.
|
||||||
change_to_child
|
changeToChild
|
||||||
else:
|
else:
|
||||||
let best_child = self.nodes[best_child_index]
|
let bestChild = self.nodes[bestChildIdx]
|
||||||
if best_child.isNone():
|
if bestChild.isNone():
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidBestDescendant,
|
kind: fcInvalidBestDescendant,
|
||||||
index: best_child_index
|
index: bestChildIdx)
|
||||||
)
|
|
||||||
|
|
||||||
let best_child_leads_to_viable_head =
|
let bestChildLeadsToViableHead =
|
||||||
? self.node_leads_to_viable_head(best_child.get())
|
? self.nodeLeadsToViableHead(bestChild.get())
|
||||||
|
|
||||||
if child_leads_to_viable_head and not best_child_leads_to_viable_head:
|
if childLeadsToViableHead and not bestChildLeadsToViableHead:
|
||||||
# The child leads to a viable head, but the current best-child doesn't
|
# The child leads to a viable head, but the current best-child doesn't
|
||||||
change_to_child
|
changeToChild
|
||||||
elif not child_leads_to_viable_head and best_child_leads_to_viable_head:
|
elif not childLeadsToViableHead and bestChildLeadsToViableHead:
|
||||||
# The best child leads to a viable head, but the child doesn't
|
# The best child leads to a viable head, but the child doesn't
|
||||||
no_change
|
noChange
|
||||||
elif child.get().weight == best_child.get().weight:
|
elif child.get().weight == bestChild.get().weight:
|
||||||
# Tie-breaker of equal weights by root
|
# Tie-breaker of equal weights by root
|
||||||
if child.get().root.tiebreak(best_child.get().root):
|
if child.get().root.tiebreak(bestChild.get().root):
|
||||||
change_to_child
|
changeToChild
|
||||||
else:
|
else:
|
||||||
no_change
|
noChange
|
||||||
else: # Choose winner by weight
|
else: # Choose winner by weight
|
||||||
let cw = child.get().weight
|
let cw = child.get().weight
|
||||||
let bw = best_child.get().weight
|
let bw = bestChild.get().weight
|
||||||
if cw >= bw:
|
if cw >= bw:
|
||||||
change_to_child
|
changeToChild
|
||||||
else:
|
else:
|
||||||
no_change
|
noChange
|
||||||
else:
|
else:
|
||||||
if child_leads_to_viable_head:
|
if childLeadsToViableHead:
|
||||||
# There is no current best-child and the child is viable
|
# There is no current best-child and the child is viable
|
||||||
change_to_child
|
changeToChild
|
||||||
else:
|
else:
|
||||||
# There is no current best-child but the child is not viable
|
# There is no current best-child but the child is not viable
|
||||||
no_change
|
noChange
|
||||||
|
|
||||||
self.nodes.buf[parent_index - self.nodes.offset].best_child = new_best_child
|
self.nodes.buf[parentIdx - self.nodes.offset].bestChild = newBestChild
|
||||||
self.nodes.buf[parent_index - self.nodes.offset].best_descendant = new_best_descendant
|
self.nodes.buf[parentIdx - self.nodes.offset].bestDescendant = newBestDescendant
|
||||||
|
|
||||||
return ok()
|
ok()
|
||||||
|
|
||||||
func node_leads_to_viable_head(
|
func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool] =
|
||||||
self: ProtoArray, node: ProtoNode
|
|
||||||
): FcResult[bool] =
|
|
||||||
## Indicates if the node itself or its best-descendant are viable
|
## Indicates if the node itself or its best-descendant are viable
|
||||||
## for blockchain head
|
## for blockchain head
|
||||||
let best_descendant_is_viable_for_head = block:
|
let bestDescendantIsViableForHead = block:
|
||||||
if node.best_descendant.isSome():
|
if node.bestDescendant.isSome():
|
||||||
let best_descendant_index = node.best_descendant.unsafeGet()
|
let bestDescendantIdx = node.bestDescendant.unsafeGet()
|
||||||
let best_descendant = self.nodes[best_descendant_index]
|
let bestDescendant = self.nodes[bestDescendantIdx]
|
||||||
if best_descendant.isNone:
|
if bestDescendant.isNone:
|
||||||
return err ForkChoiceError(
|
return err ForkChoiceError(
|
||||||
kind: fcInvalidBestDescendant,
|
kind: fcInvalidBestDescendant,
|
||||||
index: best_descendant_index
|
index: bestDescendantIdx)
|
||||||
)
|
self.nodeIsViableForHead(bestDescendant.get())
|
||||||
self.node_is_viable_for_head(best_descendant.get())
|
|
||||||
else:
|
else:
|
||||||
false
|
false
|
||||||
|
|
||||||
return ok(best_descendant_is_viable_for_head or
|
ok(bestDescendantIsViableForHead or self.nodeIsViableForHead(node))
|
||||||
self.node_is_viable_for_head(node))
|
|
||||||
|
|
||||||
func node_is_viable_for_head(self: ProtoArray, node: ProtoNode): bool =
|
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
|
||||||
## This is the equivalent of `filter_block_tree` function in eth2 spec
|
## This is the equivalent of `filter_block_tree` function in eth2 spec
|
||||||
## https://github.com/ethereum/eth2.0-specs/blob/v0.10.0/specs/phase0/fork-choice.md#filter_block_tree
|
## https://github.com/ethereum/eth2.0-specs/blob/v0.10.0/specs/phase0/fork-choice.md#filter_block_tree
|
||||||
##
|
##
|
||||||
## Any node that has a different finalized or justified epoch
|
## Any node that has a different finalized or justified epoch
|
||||||
## should not be viable for the head.
|
## should not be viable for the head.
|
||||||
(
|
(
|
||||||
(node.justified_epoch == self.justified_epoch) or
|
(node.justifiedEpoch == self.justifiedEpoch) or
|
||||||
(self.justified_epoch == Epoch(0))
|
(self.justifiedEpoch == Epoch(0))
|
||||||
) and (
|
) and (
|
||||||
(node.finalized_epoch == self.finalized_epoch) or
|
(node.finalizedEpoch == self.finalizedEpoch) or
|
||||||
(self.finalized_epoch == Epoch(0))
|
(self.finalizedEpoch == Epoch(0))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
@ -493,14 +478,12 @@ when isMainModule:
|
|||||||
|
|
||||||
doAssert tiebreak(a, b)
|
doAssert tiebreak(a, b)
|
||||||
|
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let a = Eth2Digest.fromHex("0x0000000000000002000000000000000000000000000000000000000000000000")
|
let a = Eth2Digest.fromHex("0x0000000000000002000000000000000000000000000000000000000000000000")
|
||||||
let b = Eth2Digest.fromHex("0x0000000000000001000000000000000000000000000000000000000000000000") # sha256(1)
|
let b = Eth2Digest.fromHex("0x0000000000000001000000000000000000000000000000000000000000000000") # sha256(1)
|
||||||
|
|
||||||
doAssert tiebreak(a, b)
|
doAssert tiebreak(a, b)
|
||||||
|
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let a = Eth2Digest.fromHex("0xD86E8112F3C4C4442126F8E9F44F16867DA487F29052BF91B810457DB34209A4") # sha256(2)
|
let a = Eth2Digest.fromHex("0xD86E8112F3C4C4442126F8E9F44F16867DA487F29052BF91B810457DB34209A4") # sha256(2)
|
||||||
let b = Eth2Digest.fromHex("0x7C9FA136D4413FA6173637E883B6998D32E1D675F88CDDFF9DCBCF331820F4B8") # sha256(1)
|
let b = Eth2Digest.fromHex("0x7C9FA136D4413FA6173637E883B6998D32E1D675F88CDDFF9DCBCF331820F4B8") # sha256(1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user