2018-08-21 16:21:45 +00:00
# beacon_chain
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
2018-11-29 05:23:40 +00:00
# A imcomplete implementation of the state transition function, as described
# under "Per-block processing" in https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md
2018-08-21 16:21:45 +00:00
#
2018-11-29 05:23:40 +00:00
# The code is here mainly to verify the data types and get an idea about
# missing pieces - needs testing throughout
2018-08-21 16:21:45 +00:00
2018-09-26 16:26:39 +00:00
import
2018-12-03 21:41:24 +00:00
math , options , sequtils ,
2018-11-29 22:11:05 +00:00
. / extras ,
2018-12-03 21:41:24 +00:00
. / spec / [ beaconstate , crypto , datatypes , digest , helpers , validator ] ,
2018-11-29 05:23:40 +00:00
. / ssz ,
2018-09-26 16:26:39 +00:00
milagro_crypto # nimble install https://github.com/status-im/nim-milagro-crypto@#master
2018-08-21 16:21:45 +00:00
2018-11-29 22:11:05 +00:00
# TODO there's an ugly mix of functional and procedural styles here that
# is due to how the spec is mixed as well - once we're past the prototype
# stage, this will need clearing up and unification.
2018-11-29 05:23:40 +00:00
2018-11-29 22:11:05 +00:00
func checkAttestations ( state : BeaconState ,
blck : BeaconBlock ,
2018-12-03 21:41:24 +00:00
parent_slot : uint64 ) : Option [ seq [ PendingAttestationRecord ] ] =
2018-11-29 22:11:05 +00:00
# TODO perf improvement potential..
2018-12-03 17:46:22 +00:00
if blck . attestations . len > MAX_ATTESTATIONS_PER_BLOCK :
2018-11-29 22:11:05 +00:00
return
2018-08-21 16:21:45 +00:00
2018-12-03 21:41:24 +00:00
var res : seq [ PendingAttestationRecord ]
2018-08-21 16:21:45 +00:00
for attestation in blck . attestations :
2018-11-29 05:23:40 +00:00
if attestation . data . slot < = blck . slot - MIN_ATTESTATION_INCLUSION_DELAY :
return
2018-12-03 21:41:24 +00:00
# TODO unsigned undeflow in spec
if attestation . data . slot > = max ( parent_slot . int - EPOCH_LENGTH + 1 , 0 ) . uint64 :
return
let expected_justified_slot =
if attestation . data . slot > = state . latest_state_recalculation_slot :
state . justified_slot
else :
state . previous_justified_slot
if attestation . data . justified_slot ! = expected_justified_slot :
return
let expected_justified_block_hash =
get_block_hash ( state , blck , attestation . data . justified_slot )
if attestation . data . justified_block_hash ! = expected_justified_block_hash :
return
if state . latest_crosslinks [ attestation . data . shard ] . shard_block_hash notin [
attestation . data . latest_crosslink_hash , attestation . data . shard_block_hash ] :
2018-11-29 05:23:40 +00:00
return
2018-08-21 16:21:45 +00:00
2018-11-29 05:23:40 +00:00
let attestation_participants = get_attestation_participants (
2018-12-03 21:41:24 +00:00
state , attestation . data , attestation . participation_bitfield )
2018-11-29 05:23:40 +00:00
var
agg_pubkey : ValidatorPubKey
empty = true
for attester_idx in attestation_participants :
2018-12-03 21:41:24 +00:00
let validator = state . validator_registry [ attester_idx ]
2018-11-29 05:23:40 +00:00
if empty :
agg_pubkey = validator . pubkey
empty = false
else :
agg_pubkey . combine ( validator . pubkey )
# Verify that aggregate_sig verifies using the group pubkey.
let msg = hashSSZ ( attestation . data )
2018-10-02 16:09:11 +00:00
# For now only check compilation
# doAssert attestation.aggregate_sig.verifyMessage(msg, agg_pubkey)
2018-11-29 22:11:05 +00:00
debugEcho " Aggregate sig verify message: " ,
attestation . aggregate_sig . verifyMessage ( msg , agg_pubkey )
2018-12-03 21:41:24 +00:00
res . add PendingAttestationRecord (
2018-11-29 22:11:05 +00:00
data : attestation . data ,
2018-12-03 21:41:24 +00:00
participation_bitfield : attestation . participation_bitfield ,
custody_bitfield : attestation . custody_bitfield ,
2018-11-29 22:11:05 +00:00
slot_included : blck . slot
)
some ( res )
func verifyProposerSignature ( state : BeaconState , blck : BeaconBlock ) : bool =
var blck_without_sig = blck
blck_without_sig . proposer_signature = ValidatorSig ( )
let
proposal_hash = hashSSZ ( ProposalSignedData (
slot : blck . slot ,
shard : BEACON_CHAIN_SHARD ,
block_hash : Eth2Digest ( data : hashSSZ ( blck_without_sig ) )
) )
verifyMessage (
blck . proposer_signature , proposal_hash ,
2018-12-03 21:41:24 +00:00
state . validator_registry [ get_beacon_proposer_index ( state , blck . slot ) . int ] . pubkey )
2018-11-29 22:11:05 +00:00
func processRandaoReveal ( state : var BeaconState ,
blck : BeaconBlock ,
parent_slot : uint64 ) : bool =
# Update randao skips
for slot in parentslot + 1 .. < blck . slot :
let proposer_index = get_beacon_proposer_index ( state , slot )
2018-12-03 21:41:24 +00:00
state . validator_registry [ proposer_index . int ] . randao_skips . inc ( )
2018-11-29 22:11:05 +00:00
var
proposer_index = get_beacon_proposer_index ( state , blck . slot )
2018-12-03 21:41:24 +00:00
proposer = state . validator_registry [ proposer_index . int ]
2018-11-29 22:11:05 +00:00
# Check that proposer commit and reveal match
if repeat_hash ( blck . randao_reveal , proposer . randao_skips + 1 ) ! =
proposer . randao_commitment :
return
# Update state and proposer now that we're alright
for i , b in state . randao_mix . data :
state . randao_mix . data [ i ] = b xor blck . randao_reveal . data [ i ]
proposer . randao_commitment = blck . randao_reveal
proposer . randao_skips = 0
true
2018-12-03 21:41:24 +00:00
func processPoWReceiptRoot ( state : var BeaconState , blck : BeaconBlock ) : bool =
for x in state . candidate_pow_receipt_roots . mitems ( ) :
if blck . candidate_pow_receipt_root = = x . candidate_pow_receipt_root :
x . votes . inc
return true
state . candidate_pow_receipt_roots . add CandidatePoWReceiptRootRecord (
candidate_pow_receipt_root : blck . candidate_pow_receipt_root ,
votes : 1
)
return true
func processSpecials ( state : var BeaconState , blck : BeaconBlock ) : bool =
# TODO incoming spec changes here..
true
2018-11-29 22:11:05 +00:00
func process_block * ( state : BeaconState , blck : BeaconBlock ) : Option [ BeaconState ] =
## When a new block is received, all participants must verify that the block
## makes sense and update their state accordingly. This function will return
## the new state, unless something breaks along the way
# TODO: simplistic way to be able to rollback state
var state = state
let
parent_hash = blck . ancestor_hashes [ 0 ]
slot = blck . slot
parent_slot = slot - 1 # TODO Not!! can skip slots...
# TODO actually get parent block, which means fixing up BeaconState refs above;
# there's no distinction between active/crystallized state anymore, etc.
2018-12-03 21:41:24 +00:00
state . latest_block_hashes =
append_to_recent_block_hashes ( state . latest_block_hashes , parent_slot , slot ,
2018-11-29 22:11:05 +00:00
parent_hash )
let processed_attestations = checkAttestations ( state , blck , parent_slot )
if processed_attestations . isNone :
return
2018-12-03 21:41:24 +00:00
state . latest_attestations . add processed_attestations . get ( )
2018-11-29 22:11:05 +00:00
if not verifyProposerSignature ( state , blck ) :
return
2018-08-21 16:21:45 +00:00
2018-11-29 22:11:05 +00:00
if not processRandaoReveal ( state , blck , parent_slot ) :
return
2018-08-21 16:21:45 +00:00
2018-12-03 21:41:24 +00:00
if not processPoWReceiptRoot ( state , blck ) :
return
if not processSpecials ( state , blck ) :
return
2018-11-29 22:11:05 +00:00
some ( state ) # Looks ok - move on with the updated state
2018-12-03 21:41:24 +00:00
func flatten [ T ] ( v : openArray [ seq [ T ] ] ) : seq [ T ] =
for x in v : result . add x
func get_epoch_boundary_attesters (
state : BeaconState ,
attestations : openArray [ PendingAttestationRecord ] ) : seq [ int ] =
deduplicate ( flatten ( mapIt ( attestations ,
get_attestation_participants ( state , it . data , it . participation_bitfield ) ) ) )
func adjust_for_inclusion_distance [ T ] ( magnitude : T , dist : T ) : T =
magnitude div 2 + ( magnitude div 2 ) * MIN_ATTESTATION_INCLUSION_DELAY div dist
func processEpoch * ( state : BeaconState , blck : BeaconBlock ) : Option [ BeaconState ] =
## Epoch processing happens every time we've passed EPOCH_LENGTH blocks.
## Because some slots may be skipped, it may happen that we go through the
## loop more than once - each time the latest_state_recalculation_slot will be
## increased by EPOCH_LENGTH.
# TODO: simplistic way to be able to rollback state
var state = state
# Precomputation
while blck . slot > = EPOCH_LENGTH . uint64 + state . latest_state_recalculation_slot :
let s = state . latest_state_recalculation_slot
let
active_validators =
mapIt ( get_active_validator_indices ( state . validator_registry ) ,
state . validator_registry [ it ] )
total_balance = sum ( mapIt ( active_validators , get_effective_balance ( it ) ) )
total_balance_in_eth = total_balance . int div GWEI_PER_ETH
# The per-slot maximum interest rate is `2/reward_quotient`.)
reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt ( total_balance_in_eth )
proc base_reward ( v : ValidatorRecord ) : uint64 =
get_effective_balance ( v ) div reward_quotient . uint64
# TODO doing this with iterators failed:
# https://github.com/nim-lang/Nim/issues/9827
let
this_epoch_attestations = filterIt ( state . latest_attestations ,
s < = it . data . slot and it . data . slot < s + EPOCH_LENGTH )
this_epoch_boundary_attestations = filterIt ( this_epoch_attestations ,
it . data . epoch_boundary_hash = = get_block_hash ( state , blck , s ) and
it . data . justified_slot = = state . justified_slot )
this_epoch_boundary_attesters =
get_epoch_boundary_attesters ( state , this_epoch_attestations )
this_epoch_boundary_attesting_balance = sum (
mapIt ( this_epoch_boundary_attesters ,
get_effective_balance ( state . validator_registry [ it ] ) )
)
let
previous_epoch_attestations = filterIt ( state . latest_attestations ,
s < = it . data . slot + EPOCH_LENGTH and it . data . slot < s )
previous_epoch_boundary_attestations = filterIt ( previous_epoch_attestations ,
it . data . epoch_boundary_hash = = get_block_hash ( state , blck , s ) and
it . data . justified_slot = = state . justified_slot )
previous_epoch_boundary_attesters =
get_epoch_boundary_attesters ( state , previous_epoch_boundary_attestations )
previous_epoch_boundary_attesting_balance = sum (
mapIt ( previous_epoch_boundary_attesters ,
get_effective_balance ( state . validator_registry [ it ] ) )
)
# TODO gets pretty hairy here
func attesting_validators (
obj : ShardAndCommittee , shard_block_hash : Eth2Digest ) : seq [ int ] =
flatten (
mapIt (
filterIt ( concat ( this_epoch_attestations , previous_epoch_attestations ) ,
it . data . shard = = obj . shard and
it . data . shard_block_hash = = shard_block_hash ) ,
get_attestation_participants ( state , it . data , it . participation_bitfield ) ) )
# TODO which shard_block_hash:es?
# * Let `attesting_validators(obj)` be equal to `attesting_validators(obj, shard_block_hash)` for the value of `shard_block_hash` such that `sum([get_effective_balance(v) for v in attesting_validators(obj, shard_block_hash)])` is maximized (ties broken by favoring lower `shard_block_hash` values).
# * Let `total_attesting_balance(obj)` be the sum of the balances-at-stake of `attesting_validators(obj)`.
# * Let `winning_hash(obj)` be the winning `shard_block_hash` value.
# * Let `total_balance(obj) = sum([get_effective_balance(v) for v in obj.committee])`.
# Let `inclusion_slot(v)` equal `a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`, and `inclusion_distance(v) = a.slot_included - a.data.slot` for the same attestation. We define a function `adjust_for_inclusion_distance(magnitude, distance)` which adjusts the reward of an attestation based on how long it took to get included (the longer, the lower the reward). Returns a value between 0 and `magnitude`.
# Adjust justified slots and crosslink status
var new_justified_slot : Option [ uint64 ]
# overflow intentional!
state . justified_slot_bitfield = state . justified_slot_bitfield * 2
if 3 'u64 * previous_epoch_boundary_attesting_balance > = 2 'u64 * total_balance :
# TODO spec says "flip the second lowest bit to 1" and does "AND", wrong?
state . justified_slot_bitfield = state . justified_slot_bitfield or 2
new_justified_slot = some ( s - EPOCH_LENGTH )
if 3 'u64 * this_epoch_boundary_attesting_balance > = 2 'u64 * total_balance :
# TODO spec says "flip the second lowest bit to 1" and does "AND", wrong?
state . justified_slot_bitfield = state . justified_slot_bitfield or 1
new_justified_slot = some ( s )
if state . justified_slot = = s - EPOCH_LENGTH and
state . justified_slot_bitfield mod 4 = = 3 :
state . finalized_slot = state . justified_slot
if state . justified_slot = = s - EPOCH_LENGTH - EPOCH_LENGTH and
state . justified_slot_bitfield mod 8 = = 7 :
state . finalized_slot = state . justified_slot
if state . justified_slot = = s - EPOCH_LENGTH - 2 * EPOCH_LENGTH and
state . justified_slot_bitfield mod 16 in [ 15 'u64 , 14 ] :
state . finalized_slot = state . justified_slot
state . previous_justified_slot = state . justified_slot
if new_justified_slot . isSome ( ) :
state . justified_slot = new_justified_slot . get ( )
# for obj in state.shard_and_committee_for_slots:
# 3 * total_attesting_balance(obj) >= 2 * total_balance(obj):
# state.crosslinks[shard] = CrosslinkRecord(
# slot: latest_state_recalculation_slot + EPOCH_LENGTH,
# hash: winning_hash(obj))
# Balance recalculations related to FFG rewards
let
# The portion lost by offline [validators](#dfn-validator) after `D`
# epochs is about `D*D/2/inactivity_penalty_quotient`.
inactivity_penalty_quotient = SQRT_E_DROP_TIME ^ 2
time_since_finality = blck . slot - state . finalized_slot
if time_since_finality < = 4 'u64 * EPOCH_LENGTH :
# for v in previous_epoch_boundary_attesters:
# state.validators[v].balance.inc(adjust_for_inclusion_distance(
# base_reward(state.validators[v]) *
# prev_cycle_boundary_attesting_balance div total_balance,
# inclusion_distance(v)))
for v in get_active_validator_indices ( state . validator_registry ) :
if v notin previous_epoch_boundary_attesters :
state . validator_registry [ v ] . balance . dec (
base_reward ( state . validator_registry [ v ] ) . int )
else :
# Any validator in `prev_cycle_boundary_attesters` sees their balance
# unchanged.
# Others might get penalized:
for vindex , v in state . validator_registry . mpairs ( ) :
if ( v . status = = ACTIVE and vindex notin previous_epoch_boundary_attesters ) or
v . status = = EXITED_WITH_PENALTY :
v . balance . dec (
( base_reward ( v ) + get_effective_balance ( v ) * time_since_finality div
inactivity_penalty_quotient . uint64 ) . int )
# For each `v` in `prev_cycle_boundary_attesters`, we determine the proposer `proposer_index = get_beacon_proposer_index(state, inclusion_slot(v))` and set `state.validators[proposer_index].balance += base_reward(v) // INCLUDER_REWARD_SHARE_QUOTIENT`.
# Balance recalculations related to crosslink rewards
# Ethereum 1.0 chain related rules
# Validator registry change
# If a validator registry change does NOT happen
# Proposer reshuffling
# Finally...
some ( state )