2018-11-26 13:33:06 +00:00
import
2019-02-19 23:35:02 +00:00
deques , options , sequtils , tables ,
chronicles ,
. / spec / [ beaconstate , datatypes , crypto , digest , helpers , validator ] , extras ,
. / attestation_pool , . / beacon_chain_db , . / ssz
2018-12-15 16:32:36 +00:00
2019-01-08 14:41:47 +00:00
# ##################################################################
# Specs
2019-01-08 16:07:37 +00:00
#
# The beacon chain fork choice rule is a hybrid that combines justification and finality with Latest Message Driven (LMD) Greediest Heaviest Observed SubTree (GHOST). At any point in time a [validator](#dfn-validator) `v` subjectively calculates the beacon chain head as follows.
#
# * Let `store` be the set of attestations and blocks
# that the validator `v` has observed and verified
# (in particular, block ancestors must be recursively verified).
# Attestations not part of any chain are still included in `store`.
# * Let `finalized_head` be the finalized block with the highest slot number.
# (A block `B` is finalized if there is a descendant of `B` in `store`
# the processing of which sets `B` as finalized.)
# * Let `justified_head` be the descendant of `finalized_head`
# with the highest slot number that has been justified
2019-02-20 01:33:58 +00:00
# for at least `SLOTS_PER_EPOCH` slots.
2019-01-08 16:07:37 +00:00
# (A block `B` is justified if there is a descendant of `B` in `store`
# the processing of which sets `B` as justified.)
# If no such descendant exists set `justified_head` to `finalized_head`.
# * Let `get_ancestor(store, block, slot)` be the ancestor of `block` with slot number `slot`.
# The `get_ancestor` function can be defined recursively
#
# def get_ancestor(store, block, slot):
# return block if block.slot == slot
# else get_ancestor(store, store.get_parent(block), slot)`.
#
# * Let `get_latest_attestation(store, validator)`
# be the attestation with the highest slot number in `store` from `validator`.
# If several such attestations exist,
# use the one the validator `v` observed first.
# * Let `get_latest_attestation_target(store, validator)`
# be the target block in the attestation `get_latest_attestation(store, validator)`.
# * The head is `lmd_ghost(store, justified_head)`. (See specs)
#
# Departing from specs:
# - We use a simple fork choice rule without finalized and justified head
# - We don't implement "get_latest_attestation(store, validator) -> Attestation"
# nor get_latest_attestation_target
2019-01-08 17:28:21 +00:00
# - We use block hashes (Eth2Digest) instead of raw blocks where possible
2019-02-18 18:54:05 +00:00
proc get_ancestor (
2019-02-20 01:33:58 +00:00
store : BeaconChainDB , blck : Eth2Digest , slot : Slot ) : Eth2Digest =
2019-01-08 17:28:21 +00:00
## Find the ancestor with a specific slot number
2019-02-21 21:38:26 +00:00
let blk = store . getBlock ( blck ) . get ( )
2019-01-14 12:19:44 +00:00
if blk . slot = = slot :
blck
2019-01-08 17:28:21 +00:00
else :
2019-01-14 12:19:44 +00:00
store . get_ancestor ( blk . parent_root , slot ) # TODO: Eliminate recursion
2019-01-08 17:28:21 +00:00
# TODO: what if the slot was never observed/verified?
2019-01-08 16:07:37 +00:00
2019-02-07 16:55:48 +00:00
func getVoteCount ( aggregation_bitfield : openarray [ byte ] ) : int =
2019-01-08 16:07:37 +00:00
## Get the number of votes
# TODO: A bitfield type that tracks that information
# https://github.com/status-im/nim-beacon-chain/issues/19
2019-02-07 16:55:48 +00:00
for validatorIdx in 0 .. < aggregation_bitfield . len * 8 :
result + = int aggregation_bitfield . get_bitfield_bit ( validatorIdx )
2019-01-08 16:07:37 +00:00
2019-02-18 18:54:05 +00:00
func getAttestationVoteCount (
2019-02-20 01:33:58 +00:00
pool : AttestationPool , current_slot : Slot ) : CountTable [ Eth2Digest ] =
2019-01-08 17:28:21 +00:00
## Returns all blocks more recent that the current slot
## that were attested and their vote count
2019-01-08 16:07:37 +00:00
# This replaces:
# - get_latest_attestation,
# - get_latest_attestation_targets
# that are used in lmd_ghost for
# ```
# attestation_targets = [get_latest_attestation_target(store, validator)
# for validator in active_validators]
# ```
# Note that attestation_targets in the Eth2 specs can have duplicates
# while the following implementation will count such blockhash multiple times instead.
2019-01-08 17:28:21 +00:00
result = initCountTable [ Eth2Digest ] ( )
2019-02-19 23:35:02 +00:00
# TODO iteration API that hides the startingSlot logic?
for slot in current_slot - pool . startingSlot .. < pool . slots . len . uint64 :
for attestation in pool . slots [ slot ] . attestations :
for validation in attestation . validations :
2019-01-08 17:28:21 +00:00
# Increase the block attestation counts by the number of validators aggregated
2019-02-19 23:35:02 +00:00
let voteCount = validation . aggregation_bitfield . getVoteCount ( )
result . inc ( attestation . data . beacon_block_root , voteCount )
2019-01-08 17:28:21 +00:00
2019-01-14 12:19:44 +00:00
proc lmdGhost * (
2019-01-08 17:28:21 +00:00
store : BeaconChainDB ,
pool : AttestationPool ,
state : BeaconState ,
blocksChildren : Table [ Eth2Digest , seq [ Eth2Digest ] ] ) : BeaconBlock =
# Recompute the new head of the beacon chain according to
# LMD GHOST (Latest Message Driven - Greediest Heaviest Observed SubTree)
2018-11-26 13:33:06 +00:00
2019-01-08 17:28:21 +00:00
# Raw vote count from all attestations
2019-02-12 15:56:58 +00:00
let rawVoteCount = pool . getAttestationVoteCount ( state . slot )
2019-01-08 14:41:47 +00:00
2019-01-08 17:28:21 +00:00
# The real vote count for a block also takes into account votes for its children
2019-01-08 14:41:47 +00:00
2019-01-08 17:28:21 +00:00
# TODO: a Fenwick Tree datastructure to keep track of cumulated votes
# in O(log N) complexity
# https://en.wikipedia.org/wiki/Fenwick_tree
# Nim implementation for cumulative frequencies at
# https://github.com/numforge/laser/blob/990e59fffe50779cdef33aa0b8f22da19e1eb328/benchmarks/random_sampling/fenwicktree.nim
var head = state . latest_block_roots [ state . slot mod LATEST_BLOCK_ROOTS_LENGTH ]
var childVotes = initCountTable [ Eth2Digest ] ( )
while true : # TODO use a O(log N) implementation instead of O(N^2)
let children = blocksChildren [ head ]
if children . len = = 0 :
2019-02-21 21:38:26 +00:00
return store . getBlock ( head ) . get ( )
2019-01-08 17:28:21 +00:00
# For now we assume that all children are direct descendant of the current head
2019-02-21 21:38:26 +00:00
let next_slot = store . getBlock ( head ) . get ( ) . slot + 1
2019-01-08 17:28:21 +00:00
for child in children :
2019-02-21 21:38:26 +00:00
doAssert store . getBlock ( child ) . get ( ) . slot = = next_slot
2019-01-08 17:28:21 +00:00
childVotes . clear ( )
for target , votes in rawVoteCount . pairs :
2019-02-21 21:38:26 +00:00
if store . getBlock ( target ) . get ( ) . slot > = next_slot :
2019-01-08 17:28:21 +00:00
childVotes . inc ( store . get_ancestor ( target , next_slot ) , votes )
2018-11-26 13:33:06 +00:00
2019-01-08 17:28:21 +00:00
head = childVotes . largest ( ) . key