simplify fork choice code (#1521)

* standardize init
* avoid loading state on init
* avoid some inefficient exception-based code
* remove some TODO
This commit is contained in:
Jacek Sieka 2020-08-18 16:56:32 +02:00 committed by GitHub
parent 17af7f34f4
commit 9da8b2692f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 93 deletions

View File

@ -29,11 +29,12 @@ proc init*(T: type AttestationPool, chainDag: ChainDAGRef, quarantine: Quarantin
# should probably be removed as a dependency of AttestationPool (or some other
# smart refactoring)
let tmpState = newClone(chainDag.headState)
chainDag.withState(tmpState[], chainDag.finalizedHead):
var cache = StateCache()
var forkChoice = initForkChoice(
tmpState[], getEpochInfo(chainDag.finalizedHead.blck, state, cache)).get()
let
finalizedEpochRef = chainDag.getEpochRef(
chainDag.finalizedHead.blck, chainDag.finalizedHead.slot.epoch())
var forkChoice = ForkChoice.init(
finalizedEpochRef, chainDag.finalizedHead.blck)
# Feed fork choice with unfinalized history - during startup, block pool only
# keeps track of a single history so we just need to follow it

View File

@ -28,8 +28,6 @@ export sets, results, fork_choice_types
# - Prysmatic writeup: https://hackmd.io/bABJiht3Q9SyV3Ga4FT9lQ#High-level-concept
# - Gasper Whitepaper: https://arxiv.org/abs/2003.03052
const DefaultPruneThreshold = 256
# Forward declarations
# ----------------------------------------------------------------------
@ -49,54 +47,41 @@ func compute_deltas(
logScope:
topics = "fork_choice"
proc initForkChoiceBackend*(justified_epoch: Epoch,
finalized_epoch: Epoch,
finalized_root: Eth2Digest,
): FcResult[ForkChoiceBackend] =
var proto_array = ProtoArray(
prune_threshold: DefaultPruneThreshold,
justified_epoch: finalized_epoch,
finalized_epoch: finalized_epoch
proc init*(T: type ForkChoiceBackend,
justified_epoch: Epoch,
finalized_root: Eth2Digest,
finalized_epoch: Epoch): T =
T(
proto_array: ProtoArray.init(
justified_epoch,
finalized_root,
finalized_epoch
)
)
? proto_array.on_block(
finalized_root,
hasParentInForkChoice = false,
Eth2Digest(),
finalized_epoch,
finalized_epoch
)
ok(ForkChoiceBackend(
proto_array: proto_array,
))
proc initForkChoice*(finalizedState: StateData,
epochRef: EpochRef): FcResult[ForkChoice] =
## Initialize a fork choice context
proc init*(T: type ForkChoice,
epochRef: EpochRef,
blck: BlockRef): T =
## Initialize a fork choice context for a genesis state - in the genesis
## state, the justified and finalized checkpoints are the same, so only one
## is used here
debug "Initializing fork choice",
state_epoch = finalizedState.data.data.get_current_epoch(),
blck = shortLog(finalizedState.blck)
let finalized_epoch = finalizedState.data.data.get_current_epoch()
epoch = epochRef.epoch, blck = shortLog(blck)
let
justified = BalanceCheckpoint(
blck: finalizedState.blck, epochRef: epochRef)
finalized = Checkpoint(
root: finalizedState.blck.root, epoch: finalized_epoch)
justified = BalanceCheckpoint(blck: blck, epochRef: epochRef)
finalized = Checkpoint(root: blck.root, epoch: epochRef.epoch)
best_justified = Checkpoint(
root: justified.blck.root, epoch: justified.epochRef.epoch)
let backend = ? initForkChoiceBackend(
finalized_epoch, finalized_epoch, finalizedState.blck.root)
ok(ForkChoice(
backend: backend,
ForkChoice(
backend: ForkChoiceBackend.init(
epochRef.epoch, blck.root, epochRef.epoch),
checkpoints: Checkpoints(
justified: justified,
best_justified:
Checkpoint(root: justified.blck.root, epoch: justified.epochRef.epoch),
finalized: finalized)
))
finalized: finalized,
best_justified: best_justified)
)
func extend[T](s: var seq[T], minLen: int) =
## Extend a sequence so that it can contains at least `minLen` elements.
@ -288,8 +273,7 @@ proc process_block*(self: var ForkChoiceBackend,
justified_epoch: Epoch,
finalized_epoch: Epoch): FcResult[void] =
self.proto_array.on_block(
block_root, hasParentInForkChoice = true, parent_root,
justified_epoch, finalized_epoch)
block_root, parent_root, justified_epoch, finalized_epoch)
proc process_block*(self: var ForkChoice,
dag: ChainDAGRef,

View File

@ -23,6 +23,8 @@ logScope:
export results
const DefaultPruneThreshold* = 256
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/fork-choice.md
# This is a port of https://github.com/sigp/lighthouse/pull/804
# which is a port of "Proto-Array": https://github.com/protolambda/lmd-ghost
@ -46,24 +48,14 @@ func tiebreak(a, b: Eth2Digest): bool =
# else we have equality so far
return true
template getOrFailcase*[K, V](table: Table[K, V], key: K, failcase: untyped): V =
## Get a value from a Nim Table, turning KeyError into
## the "failcase"
block:
# TODO: try/except expression with Nim v1.2.0:
# https://github.com/status-im/nim-beacon-chain/pull/865#discussion_r404856551
var value: V
try:
value = table[key]
except KeyError:
failcase
value
template unsafeGet*[K, V](table: Table[K, V], key: K): V =
## Get a value from a Nim Table, turning KeyError into
## an AssertionError defect
getOrFailcase(table, key):
doAssert false, "The " & astToStr(table) & " table shouldn't miss a key"
# Pointer is used to work around the lack of a `var` withValue
try:
table[key]
except KeyError as exc:
raiseAssert(exc.msg)
# Forward declarations
# ----------------------------------------------------------------------
@ -76,6 +68,29 @@ func node_leads_to_viable_head(self: ProtoArray, node: ProtoNode): FcResult[bool
# ProtoArray routines
# ----------------------------------------------------------------------
func init*(T: type ProtoArray,
justified_epoch: Epoch,
finalized_root: Eth2Digest,
finalized_epoch: Epoch,
prune_threshold = DefaultPruneThreshold): T =
let node = ProtoNode(
root: finalized_root,
parent: none(int),
justified_epoch: justified_epoch,
finalized_epoch: finalized_epoch,
weight: 0,
best_child: none(int),
best_descendant: none(int)
)
T(
prune_threshold: DefaultPruneThreshold,
justified_epoch: justified_epoch,
finalized_epoch: finalized_epoch,
nodes: @[node],
indices: {node.root: 0}.toTable()
)
func apply_score_changes*(
self: var ProtoArray,
deltas: var openarray[Delta],
@ -158,7 +173,6 @@ func apply_score_changes*(
func on_block*(
self: var ProtoArray,
root: Eth2Digest,
hasParentInForkChoice: bool,
parent: Eth2Digest,
justified_epoch: Epoch,
finalized_epoch: Epoch
@ -175,24 +189,21 @@ func on_block*(
if root in self.indices:
return ok()
var parent_index: Option[int]
if not hasParentInForkChoice:
# Genesis (but Genesis might not be default(Eth2Digest))
parent_index = none(int)
elif parent notin self.indices:
var parent_index: Index
self.indices.withValue(parent, index) do:
parent_index = index[]
do:
return err ForkChoiceError(
kind: fcUnknownParent,
child_root: root,
parent_root: parent
)
else:
parent_index = some(self.indices.unsafeGet(parent))
let node_index = self.nodes.len
let node = ProtoNode(
root: root,
parent: parent_index,
parent: some(parent_index),
justified_epoch: justified_epoch,
finalized_epoch: finalized_epoch,
weight: 0,
@ -201,10 +212,9 @@ func on_block*(
)
self.indices[node.root] = node_index
self.nodes.add node # TODO: if this is costly, we can setLen + construct the node in-place
self.nodes.add node
if parent_index.isSome(): # parent_index is always valid except for Genesis
? self.maybe_update_best_child_and_descendant(parent_index.unsafeGet(), node_index)
? self.maybe_update_best_child_and_descendant(parent_index, node_index)
return ok()
@ -220,7 +230,10 @@ func find_head*(
## is not followed by `apply_score_changes` as `on_new_block` does not
## update the whole tree.
let justified_index = self.indices.getOrFailcase(justified_root):
var justified_index: Index
self.indices.withValue(justified_root, value) do:
justified_index = value[]
do:
return err ForkChoiceError(
kind: fcJustifiedNodeUnknown,
block_root: justified_root
@ -235,11 +248,7 @@ func find_head*(
template justified_node: untyped = self.nodes[justified_index]
# Alias, IndexError are defects
let best_descendant_index = block:
if justified_node.best_descendant.isSome():
justified_node.best_descendant.unsafeGet()
else:
justified_index
let best_descendant_index = justified_node.best_descendant.get(justified_index)
if best_descendant_index notin {0..self.nodes.len-1}:
return err ForkChoiceError(
@ -281,7 +290,11 @@ func maybe_prune*(
## - The finalized epoch is less than the current one
## - The finalized epoch matches the current one but the finalized root is different
## - Internal error due to invalid indices in `self`
let finalized_index = self.indices.getOrFailcase(finalized_root):
var finalized_index: int
self.indices.withValue(finalized_root, value) do:
finalized_index = value[]
do:
return err ForkChoiceError(
kind: fcFinalizedNodeUnknown,
block_root: finalized_root
@ -382,8 +395,8 @@ func maybe_update_best_child_and_descendant(
)
# Aliases
template child: untyped {.dirty.} = self.nodes[child_index]
template parent: untyped {.dirty.} = self.nodes[parent_index]
template child: untyped = self.nodes[child_index]
template parent: untyped = self.nodes[parent_index]
let child_leads_to_viable_head = ? self.node_leads_to_viable_head(child)

View File

@ -12,11 +12,11 @@ proc setup_finality_01(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
let GenesisRoot = fakeHash(0)
# Initialize the fork choice context
result.fork_choice = initForkChoiceBackend(
result.fork_choice = ForkChoiceBackend.init(
justified_epoch = Epoch(1),
finalized_epoch = Epoch(1),
finalized_root = GenesisRoot
).get()
)
# ----------------------------------

View File

@ -12,11 +12,11 @@ proc setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
let GenesisRoot = fakeHash(0)
# Initialize the fork choice context
result.fork_choice = initForkChoiceBackend(
result.fork_choice = ForkChoiceBackend.init(
justified_epoch = Epoch(1),
finalized_epoch = Epoch(1),
finalized_root = GenesisRoot
).get()
)
# ----------------------------------

View File

@ -12,11 +12,11 @@ proc setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
let GenesisRoot = fakeHash(0)
# Initialize the fork choice context
result.fork_choice = initForkChoiceBackend(
result.fork_choice = ForkChoiceBackend.init(
justified_epoch = Epoch(1),
finalized_epoch = Epoch(1),
finalized_root = GenesisRoot
).get()
)
# ----------------------------------

View File

@ -12,11 +12,11 @@ proc setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
let GenesisRoot = fakeHash(0)
# Initialize the fork choice context
result.fork_choice = initForkChoiceBackend(
result.fork_choice = ForkChoiceBackend.init(
justified_epoch = Epoch(1),
finalized_epoch = Epoch(1),
finalized_root = GenesisRoot
).get()
)
# ----------------------------------