Split state_transition (#301)
This commit is contained in:
parent
0d37ed7a2a
commit
214860cb88
|
@ -0,0 +1,461 @@
|
||||||
|
# beacon_chain
|
||||||
|
# Copyright (c) 2018-2019 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.
|
||||||
|
|
||||||
|
# State transition - block processing, as described in
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#beacon-chain-state-transition-function
|
||||||
|
#
|
||||||
|
# The purpose of this code right is primarily educational, to help piece
|
||||||
|
# together the mechanics of the beacon state and to discover potential problem
|
||||||
|
# areas.
|
||||||
|
#
|
||||||
|
# The entry point is `process_block` which is at the bottom of this file.
|
||||||
|
#
|
||||||
|
# General notes about the code (TODO):
|
||||||
|
# * It's inefficient - we quadratically copy, allocate and iterate when there
|
||||||
|
# are faster options
|
||||||
|
# * Weird styling - the sections taken from the spec use python styling while
|
||||||
|
# the others use NEP-1 - helps grepping identifiers in spec
|
||||||
|
# * We mix procedural and functional styles for no good reason, except that the
|
||||||
|
# spec does so also.
|
||||||
|
# * There are likely lots of bugs.
|
||||||
|
# * For indices, we get a mix of uint64, ValidatorIndex and int - this is currently
|
||||||
|
# swept under the rug with casts
|
||||||
|
# * The spec uses uint64 for data types, but functions in the spec often assume
|
||||||
|
# signed bigint semantics - under- and overflows ensue
|
||||||
|
# * Sane error handling is missing in most cases (yay, we'll get the chance to
|
||||||
|
# debate exceptions again!)
|
||||||
|
# When updating the code, add TODO sections to mark where there are clear
|
||||||
|
# improvements to be made - other than that, keep things similar to spec for
|
||||||
|
# now.
|
||||||
|
|
||||||
|
import # TODO - cleanup imports
|
||||||
|
algorithm, collections/sets, chronicles, math, options, sequtils, sets, tables,
|
||||||
|
../extras, ../ssz, ../beacon_node_types,
|
||||||
|
beaconstate, bitfield, crypto, datatypes, digest, helpers, validator
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#block-header
|
||||||
|
proc processBlockHeader(
|
||||||
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
||||||
|
stateCache: var StateCache): bool =
|
||||||
|
# Verify that the slots match
|
||||||
|
if not (blck.slot == state.slot):
|
||||||
|
notice "Block header: slot mismatch",
|
||||||
|
block_slot = humaneSlotNum(blck.slot),
|
||||||
|
state_slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify that the parent matches
|
||||||
|
if skipValidation notin flags and not (blck.parent_root ==
|
||||||
|
signing_root(state.latest_block_header)):
|
||||||
|
notice "Block header: previous block root mismatch",
|
||||||
|
latest_block_header = state.latest_block_header,
|
||||||
|
blck = shortLog(blck),
|
||||||
|
latest_block_header_root = shortLog(signing_root(state.latest_block_header))
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Save current block as the new latest block
|
||||||
|
state.latest_block_header = BeaconBlockHeader(
|
||||||
|
slot: blck.slot,
|
||||||
|
parent_root: blck.parent_root,
|
||||||
|
body_root: hash_tree_root(blck.body),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify proposer is not slashed
|
||||||
|
let proposer =
|
||||||
|
state.validator_registry[get_beacon_proposer_index(state, stateCache)]
|
||||||
|
if proposer.slashed:
|
||||||
|
notice "Block header: proposer slashed"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify proposer signature
|
||||||
|
if skipValidation notin flags and not bls_verify(
|
||||||
|
proposer.pubkey,
|
||||||
|
signing_root(blck).data,
|
||||||
|
blck.signature,
|
||||||
|
get_domain(state, DOMAIN_BEACON_PROPOSER)):
|
||||||
|
notice "Block header: invalid block header",
|
||||||
|
proposer_pubkey = proposer.pubkey,
|
||||||
|
block_root = shortLog(signing_root(blck)),
|
||||||
|
block_signature = blck.signature
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#randao
|
||||||
|
proc processRandao(
|
||||||
|
state: var BeaconState, body: BeaconBlockBody, flags: UpdateFlags,
|
||||||
|
stateCache: var StateCache): bool =
|
||||||
|
let
|
||||||
|
proposer_index = get_beacon_proposer_index(state, stateCache)
|
||||||
|
proposer = addr state.validator_registry[proposer_index]
|
||||||
|
|
||||||
|
# Verify that the provided randao value is valid
|
||||||
|
if skipValidation notin flags:
|
||||||
|
if not bls_verify(
|
||||||
|
proposer.pubkey,
|
||||||
|
hash_tree_root(get_current_epoch(state).uint64).data,
|
||||||
|
body.randao_reveal,
|
||||||
|
get_domain(state, DOMAIN_RANDAO)):
|
||||||
|
|
||||||
|
notice "Randao mismatch", proposer_pubkey = proposer.pubkey,
|
||||||
|
message = get_current_epoch(state),
|
||||||
|
signature = body.randao_reveal,
|
||||||
|
slot = state.slot
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Mix it in
|
||||||
|
let
|
||||||
|
mix = get_current_epoch(state) mod LATEST_RANDAO_MIXES_LENGTH
|
||||||
|
rr = eth2hash(body.randao_reveal.getBytes()).data
|
||||||
|
|
||||||
|
for i, b in state.latest_randao_mixes[mix].data:
|
||||||
|
state.latest_randao_mixes[mix].data[i] = b xor rr[i]
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#eth1-data
|
||||||
|
func processEth1Data(state: var BeaconState, body: BeaconBlockBody) =
|
||||||
|
state.eth1_data_votes.add body.eth1_data
|
||||||
|
if state.eth1_data_votes.count(body.eth1_data) * 2 >
|
||||||
|
SLOTS_PER_ETH1_VOTING_PERIOD:
|
||||||
|
state.latest_eth1_data = body.eth1_data
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#is_slashable_validator
|
||||||
|
func is_slashable_validator(validator: Validator, epoch: Epoch): bool =
|
||||||
|
# Check if ``validator`` is slashable.
|
||||||
|
(not validator.slashed) and
|
||||||
|
(validator.activation_epoch <= epoch) and
|
||||||
|
(epoch < validator.withdrawable_epoch)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#proposer-slashings
|
||||||
|
proc processProposerSlashings(
|
||||||
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
||||||
|
stateCache: var StateCache): bool =
|
||||||
|
if len(blck.body.proposer_slashings) > MAX_PROPOSER_SLASHINGS:
|
||||||
|
notice "PropSlash: too many!",
|
||||||
|
proposer_slashings = len(blck.body.proposer_slashings)
|
||||||
|
return false
|
||||||
|
|
||||||
|
for proposer_slashing in blck.body.proposer_slashings:
|
||||||
|
let proposer = state.validator_registry[proposer_slashing.proposer_index.int]
|
||||||
|
|
||||||
|
# Verify that the epoch is the same
|
||||||
|
if not (slot_to_epoch(proposer_slashing.header_1.slot) ==
|
||||||
|
slot_to_epoch(proposer_slashing.header_2.slot)):
|
||||||
|
notice "PropSlash: epoch mismatch"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# But the headers are different
|
||||||
|
if not (proposer_slashing.header_1 != proposer_slashing.header_2):
|
||||||
|
notice "PropSlash: headers not different"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Check proposer is slashable
|
||||||
|
if not is_slashable_validator(proposer, get_current_epoch(state)):
|
||||||
|
notice "PropSlash: slashed proposer"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Signatures are valid
|
||||||
|
if skipValidation notin flags:
|
||||||
|
for i, header in @[proposer_slashing.header_1, proposer_slashing.header_2]:
|
||||||
|
if not bls_verify(
|
||||||
|
proposer.pubkey,
|
||||||
|
signing_root(header).data,
|
||||||
|
header.signature,
|
||||||
|
get_domain(
|
||||||
|
state, DOMAIN_BEACON_PROPOSER, slot_to_epoch(header.slot))):
|
||||||
|
notice "PropSlash: invalid signature",
|
||||||
|
signature_index = i
|
||||||
|
return false
|
||||||
|
|
||||||
|
slashValidator(
|
||||||
|
state, proposer_slashing.proposer_index.ValidatorIndex, stateCache)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#is_slashable_attestation_data
|
||||||
|
func is_slashable_attestation_data(
|
||||||
|
data_1: AttestationData, data_2: AttestationData): bool =
|
||||||
|
## Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG
|
||||||
|
## rules.
|
||||||
|
|
||||||
|
# Double vote
|
||||||
|
(data_1 != data_2 and data_1.target_epoch == data_2.target_epoch) or
|
||||||
|
# Surround vote
|
||||||
|
(data_1.source_epoch < data_2.source_epoch and
|
||||||
|
data_2.target_epoch < data_1.target_epoch)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#attester-slashings
|
||||||
|
proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock,
|
||||||
|
stateCache: var StateCache): bool =
|
||||||
|
# Process ``AttesterSlashing`` operation.
|
||||||
|
if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS:
|
||||||
|
notice "CaspSlash: too many!"
|
||||||
|
return false
|
||||||
|
|
||||||
|
result = true
|
||||||
|
for attester_slashing in blck.body.attester_slashings:
|
||||||
|
let
|
||||||
|
attestation_1 = attester_slashing.attestation_1
|
||||||
|
attestation_2 = attester_slashing.attestation_2
|
||||||
|
|
||||||
|
if not is_slashable_attestation_data(
|
||||||
|
attestation_1.data, attestation_2.data):
|
||||||
|
notice "CaspSlash: surround or double vote check failed"
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not validate_indexed_attestation(state, attestation_1):
|
||||||
|
notice "CaspSlash: invalid votes 1"
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not validate_indexed_attestation(state, attestation_2):
|
||||||
|
notice "CaspSlash: invalid votes 2"
|
||||||
|
return false
|
||||||
|
|
||||||
|
var slashed_any = false
|
||||||
|
|
||||||
|
## TODO there's a lot of sorting/set construction here and
|
||||||
|
## verify_indexed_attestation, but go by spec unless there
|
||||||
|
## is compelling perf evidence otherwise.
|
||||||
|
let attesting_indices_1 =
|
||||||
|
attestation_1.custody_bit_0_indices & attestation_1.custody_bit_1_indices
|
||||||
|
let attesting_indices_2 =
|
||||||
|
attestation_2.custody_bit_0_indices & attestation_2.custody_bit_1_indices
|
||||||
|
for index in sorted(toSeq(intersection(toSet(attesting_indices_1),
|
||||||
|
toSet(attesting_indices_2)).items), system.cmp):
|
||||||
|
if is_slashable_validator(state.validator_registry[index.int],
|
||||||
|
get_current_epoch(state)):
|
||||||
|
slash_validator(state, index.ValidatorIndex, stateCache)
|
||||||
|
slashed_any = true
|
||||||
|
result = result and slashed_any
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestations
|
||||||
|
proc processAttestations(
|
||||||
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
||||||
|
stateCache: var StateCache): bool =
|
||||||
|
## Each block includes a number of attestations that the proposer chose. Each
|
||||||
|
## attestation represents an update to a specific shard and is signed by a
|
||||||
|
## committee of validators.
|
||||||
|
## Here we make sanity checks for each attestation and it to the state - most
|
||||||
|
## updates will happen at the epoch boundary where state updates happen in
|
||||||
|
## bulk.
|
||||||
|
if blck.body.attestations.len > MAX_ATTESTATIONS:
|
||||||
|
notice "Attestation: too many!", attestations = blck.body.attestations.len
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not blck.body.attestations.allIt(checkAttestation(state, it, flags, stateCache)):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# All checks passed - update state
|
||||||
|
# Apply the attestations
|
||||||
|
var committee_count_cache = initTable[Epoch, uint64]()
|
||||||
|
|
||||||
|
for attestation in blck.body.attestations:
|
||||||
|
# Caching
|
||||||
|
let
|
||||||
|
epoch = attestation.data.target_epoch
|
||||||
|
committee_count = if epoch in committee_count_cache:
|
||||||
|
committee_count_cache[epoch]
|
||||||
|
else:
|
||||||
|
get_epoch_committee_count(state, epoch)
|
||||||
|
committee_count_cache[epoch] = committee_count
|
||||||
|
|
||||||
|
# Spec content
|
||||||
|
let attestation_slot =
|
||||||
|
get_attestation_data_slot(state, attestation.data, committee_count)
|
||||||
|
let pending_attestation = PendingAttestation(
|
||||||
|
data: attestation.data,
|
||||||
|
aggregation_bitfield: attestation.aggregation_bitfield,
|
||||||
|
inclusion_delay: state.slot - attestation_slot,
|
||||||
|
proposer_index: get_beacon_proposer_index(state, stateCache),
|
||||||
|
)
|
||||||
|
|
||||||
|
if attestation.data.target_epoch == get_current_epoch(state):
|
||||||
|
state.current_epoch_attestations.add(pending_attestation)
|
||||||
|
else:
|
||||||
|
state.previous_epoch_attestations.add(pending_attestation)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#deposits
|
||||||
|
proc processDeposits(state: var BeaconState, blck: BeaconBlock): bool =
|
||||||
|
if not (len(blck.body.deposits) <= MAX_DEPOSITS):
|
||||||
|
notice "processDeposits: too many deposits"
|
||||||
|
return false
|
||||||
|
|
||||||
|
for deposit in blck.body.deposits:
|
||||||
|
if not process_deposit(state, deposit):
|
||||||
|
notice "processDeposits: deposit invalid"
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#voluntary-exits
|
||||||
|
proc processVoluntaryExits(
|
||||||
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||||
|
# Process ``VoluntaryExit`` transaction.
|
||||||
|
if len(blck.body.voluntary_exits) > MAX_VOLUNTARY_EXITS:
|
||||||
|
notice "Exit: too many!"
|
||||||
|
return false
|
||||||
|
|
||||||
|
for exit in blck.body.voluntary_exits:
|
||||||
|
let validator = state.validator_registry[exit.validator_index.int]
|
||||||
|
|
||||||
|
# Verify the validator is active
|
||||||
|
if not is_active_validator(validator, get_current_epoch(state)):
|
||||||
|
notice "Exit: validator not active"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify the validator has not yet exited
|
||||||
|
if not (validator.exit_epoch == FAR_FUTURE_EPOCH):
|
||||||
|
notice "Exit: validator has exited"
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Exits must specify an epoch when they become valid; they are not valid
|
||||||
|
## before then
|
||||||
|
if not (get_current_epoch(state) >= exit.epoch):
|
||||||
|
notice "Exit: exit epoch not passed"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify the validator has been active long enough
|
||||||
|
# TODO detect underflow
|
||||||
|
if not (get_current_epoch(state) - validator.activation_epoch >=
|
||||||
|
PERSISTENT_COMMITTEE_PERIOD):
|
||||||
|
notice "Exit: not in validator set long enough"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify signature
|
||||||
|
if skipValidation notin flags:
|
||||||
|
if not bls_verify(
|
||||||
|
validator.pubkey, signing_root(exit).data, exit.signature,
|
||||||
|
get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch)):
|
||||||
|
notice "Exit: invalid signature"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Initiate exit
|
||||||
|
initiate_validator_exit(state, exit.validator_index.ValidatorIndex)
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#transfers
|
||||||
|
proc processTransfers(state: var BeaconState, blck: BeaconBlock,
|
||||||
|
flags: UpdateFlags, stateCache: var StateCache): bool =
|
||||||
|
if not (len(blck.body.transfers) <= MAX_TRANSFERS):
|
||||||
|
notice "Transfer: too many transfers"
|
||||||
|
return false
|
||||||
|
|
||||||
|
for transfer in blck.body.transfers:
|
||||||
|
let sender_balance = state.balances[transfer.sender.int]
|
||||||
|
|
||||||
|
## Verify the amount and fee are not individually too big (for anti-overflow
|
||||||
|
## purposes)
|
||||||
|
if not (sender_balance >= max(transfer.amount, transfer.fee)):
|
||||||
|
notice "Transfer: sender balance too low for transfer amount or fee"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# A transfer is valid in only one slot
|
||||||
|
if not (state.slot == transfer.slot):
|
||||||
|
notice "Transfer: slot mismatch"
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Sender must be not yet eligible for activation, withdrawn, or transfer
|
||||||
|
## balance over MAX_EFFECTIVE_BALANCE
|
||||||
|
if not (
|
||||||
|
state.validator_registry[transfer.sender.int].activation_epoch ==
|
||||||
|
FAR_FUTURE_EPOCH or
|
||||||
|
get_current_epoch(state) >=
|
||||||
|
state.validator_registry[
|
||||||
|
transfer.sender.int].withdrawable_epoch or
|
||||||
|
transfer.amount + transfer.fee + MAX_EFFECTIVE_BALANCE <=
|
||||||
|
state.balances[transfer.sender.int]):
|
||||||
|
notice "Transfer: only withdrawn or not-activated accounts with sufficient balance can transfer"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify that the pubkey is valid
|
||||||
|
let wc = state.validator_registry[transfer.sender.int].
|
||||||
|
withdrawal_credentials
|
||||||
|
if not (wc.data[0] == BLS_WITHDRAWAL_PREFIX and
|
||||||
|
wc.data[1..^1] == eth2hash(transfer.pubkey.getBytes).data[1..^1]):
|
||||||
|
notice "Transfer: incorrect withdrawal credentials"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify that the signature is valid
|
||||||
|
if skipValidation notin flags:
|
||||||
|
if not bls_verify(
|
||||||
|
transfer.pubkey, signing_root(transfer).data, transfer.signature,
|
||||||
|
get_domain(state, DOMAIN_TRANSFER)):
|
||||||
|
notice "Transfer: incorrect signature"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Process the transfer
|
||||||
|
decrease_balance(
|
||||||
|
state, transfer.sender.ValidatorIndex, transfer.amount + transfer.fee)
|
||||||
|
increase_balance(
|
||||||
|
state, transfer.recipient.ValidatorIndex, transfer.amount)
|
||||||
|
increase_balance(
|
||||||
|
state, get_beacon_proposer_index(state, stateCache), transfer.fee)
|
||||||
|
|
||||||
|
# Verify balances are not dust
|
||||||
|
if not (
|
||||||
|
0'u64 < state.balances[transfer.sender.int] and
|
||||||
|
state.balances[transfer.sender.int] < MIN_DEPOSIT_AMOUNT):
|
||||||
|
notice "Transfer: sender balance too low for transfer amount or fee"
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not (
|
||||||
|
0'u64 < state.balances[transfer.recipient.int] and
|
||||||
|
state.balances[transfer.recipient.int] < MIN_DEPOSIT_AMOUNT):
|
||||||
|
notice "Transfer: sender balance too low for transfer amount or fee"
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
|
||||||
|
proc processBlock*(
|
||||||
|
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
||||||
|
stateCache: var StateCache): bool =
|
||||||
|
## When there's a new block, we need to verify that the block is sane and
|
||||||
|
## update the state accordingly
|
||||||
|
|
||||||
|
# TODO when there's a failure, we should reset the state!
|
||||||
|
# TODO probably better to do all verification first, then apply state changes
|
||||||
|
|
||||||
|
if not processBlockHeader(state, blck, flags, stateCache):
|
||||||
|
notice "Block header not valid", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not processRandao(state, blck.body, flags, stateCache):
|
||||||
|
debug "[Block processing] Randao failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
processEth1Data(state, blck.body)
|
||||||
|
|
||||||
|
if not processProposerSlashings(state, blck, flags, stateCache):
|
||||||
|
debug "[Block processing] Proposer slashing failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not processAttesterSlashings(state, blck, stateCache):
|
||||||
|
debug "[Block processing] Attester slashing failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not processAttestations(state, blck, flags, stateCache):
|
||||||
|
debug "[Block processing] Attestation processing failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not processDeposits(state, blck):
|
||||||
|
debug "[Block processing] Deposit processing failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not processVoluntaryExits(state, blck, flags):
|
||||||
|
debug "[Block processing] Exit processing failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if not processTransfers(state, blck, flags, stateCache):
|
||||||
|
debug "[Block processing] Transfer processing failure", slot = humaneSlotNum(state.slot)
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
|
@ -0,0 +1,462 @@
|
||||||
|
# beacon_chain
|
||||||
|
# Copyright (c) 2018-2019 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.
|
||||||
|
|
||||||
|
# State transition - epoch processing, as described in
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#beacon-chain-state-transition-function
|
||||||
|
#
|
||||||
|
# The purpose of this code right is primarily educational, to help piece
|
||||||
|
# together the mechanics of the beacon state and to discover potential problem
|
||||||
|
# areas.
|
||||||
|
#
|
||||||
|
# The entry point is `process_epoch` which is at the bottom of this file.
|
||||||
|
#
|
||||||
|
# General notes about the code (TODO):
|
||||||
|
# * It's inefficient - we quadratically copy, allocate and iterate when there
|
||||||
|
# are faster options
|
||||||
|
# * Weird styling - the sections taken from the spec use python styling while
|
||||||
|
# the others use NEP-1 - helps grepping identifiers in spec
|
||||||
|
# * We mix procedural and functional styles for no good reason, except that the
|
||||||
|
# spec does so also.
|
||||||
|
# * There are likely lots of bugs.
|
||||||
|
# * For indices, we get a mix of uint64, ValidatorIndex and int - this is currently
|
||||||
|
# swept under the rug with casts
|
||||||
|
# * The spec uses uint64 for data types, but functions in the spec often assume
|
||||||
|
# signed bigint semantics - under- and overflows ensue
|
||||||
|
# * Sane error handling is missing in most cases (yay, we'll get the chance to
|
||||||
|
# debate exceptions again!)
|
||||||
|
# When updating the code, add TODO sections to mark where there are clear
|
||||||
|
# improvements to be made - other than that, keep things similar to spec for
|
||||||
|
# now.
|
||||||
|
|
||||||
|
import # TODO - cleanup imports
|
||||||
|
algorithm, collections/sets, chronicles, math, options, sequtils, sets, tables,
|
||||||
|
../extras, ../ssz, ../beacon_node_types,
|
||||||
|
beaconstate, bitfield, crypto, datatypes, digest, helpers, validator
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#helper-functions-1
|
||||||
|
func get_total_active_balance(state: BeaconState): Gwei =
|
||||||
|
return get_total_balance(
|
||||||
|
state,
|
||||||
|
get_active_validator_indices(state, get_current_epoch(state)))
|
||||||
|
|
||||||
|
func get_matching_source_attestations(state: BeaconState, epoch: Epoch):
|
||||||
|
seq[PendingAttestation] =
|
||||||
|
doAssert epoch in @[get_current_epoch(state), get_previous_epoch(state)]
|
||||||
|
if epoch == get_current_epoch(state):
|
||||||
|
state.current_epoch_attestations
|
||||||
|
else:
|
||||||
|
state.previous_epoch_attestations
|
||||||
|
|
||||||
|
func get_matching_target_attestations(state: BeaconState, epoch: Epoch):
|
||||||
|
seq[PendingAttestation] =
|
||||||
|
filterIt(
|
||||||
|
get_matching_source_attestations(state, epoch),
|
||||||
|
it.data.target_root == get_block_root(state, epoch)
|
||||||
|
)
|
||||||
|
|
||||||
|
func get_matching_head_attestations(state: BeaconState, epoch: Epoch):
|
||||||
|
seq[PendingAttestation] =
|
||||||
|
filterIt(
|
||||||
|
get_matching_source_attestations(state, epoch),
|
||||||
|
it.data.beacon_block_root ==
|
||||||
|
get_block_root_at_slot(state, get_attestation_data_slot(state, it.data))
|
||||||
|
)
|
||||||
|
|
||||||
|
func get_unslashed_attesting_indices(
|
||||||
|
state: BeaconState, attestations: seq[PendingAttestation],
|
||||||
|
stateCache: var StateCache): HashSet[ValidatorIndex] =
|
||||||
|
result = initSet[ValidatorIndex]()
|
||||||
|
for a in attestations:
|
||||||
|
result = result.union(get_attesting_indices(
|
||||||
|
state, a.data, a.aggregation_bitfield, stateCache))
|
||||||
|
|
||||||
|
for index in result:
|
||||||
|
if state.validator_registry[index].slashed:
|
||||||
|
result.excl index
|
||||||
|
|
||||||
|
func get_attesting_balance(
|
||||||
|
state: BeaconState, attestations: seq[PendingAttestation],
|
||||||
|
stateCache: var StateCache): Gwei =
|
||||||
|
get_total_balance(state, get_unslashed_attesting_indices(
|
||||||
|
state, attestations, stateCache))
|
||||||
|
|
||||||
|
# Not exactly in spec, but for get_winning_crosslink_and_attesting_indices
|
||||||
|
func lowerThan(candidate, current: Eth2Digest): bool =
|
||||||
|
# return true iff candidate is "lower" than current, per spec rule:
|
||||||
|
# "ties broken in favor of lexicographically higher hash
|
||||||
|
for i, v in current.data:
|
||||||
|
if v > candidate.data[i]: return true
|
||||||
|
false
|
||||||
|
|
||||||
|
func get_winning_crosslink_and_attesting_indices(
|
||||||
|
state: BeaconState, epoch: Epoch, shard: Shard,
|
||||||
|
stateCache: var StateCache): tuple[a: Crosslink, b: HashSet[ValidatorIndex]] =
|
||||||
|
let
|
||||||
|
attestations =
|
||||||
|
filterIt(
|
||||||
|
get_matching_source_attestations(state, epoch),
|
||||||
|
it.data.crosslink.shard == shard)
|
||||||
|
# TODO don't keep h_t_r'ing state.current_crosslinks[shard]
|
||||||
|
crosslinks =
|
||||||
|
filterIt(
|
||||||
|
mapIt(attestations, it.data.crosslink),
|
||||||
|
hash_tree_root(state.current_crosslinks[shard]) in
|
||||||
|
# TODO pointless memory allocation, etc.
|
||||||
|
@[it.parent_root, hash_tree_root(it)])
|
||||||
|
|
||||||
|
# default=Crosslink()
|
||||||
|
if len(crosslinks) == 0:
|
||||||
|
return (Crosslink(), initSet[ValidatorIndex]())
|
||||||
|
|
||||||
|
## Winning crosslink has the crosslink data root with the most balance voting
|
||||||
|
## for it (ties broken lexicographically)
|
||||||
|
var
|
||||||
|
winning_crosslink: Crosslink
|
||||||
|
winning_crosslink_balance = 0.Gwei
|
||||||
|
|
||||||
|
for candidate_crosslink in crosslinks:
|
||||||
|
## TODO check if should cache this again
|
||||||
|
## let root_balance = get_attesting_balance_cached(
|
||||||
|
## state, attestations_for.getOrDefault(r), cache)
|
||||||
|
let crosslink_balance =
|
||||||
|
get_attesting_balance(
|
||||||
|
state,
|
||||||
|
filterIt(attestations, it.data.crosslink == candidate_crosslink),
|
||||||
|
stateCache)
|
||||||
|
if (crosslink_balance > winning_crosslink_balance or
|
||||||
|
(winning_crosslink_balance == crosslink_balance and
|
||||||
|
lowerThan(winning_crosslink.data_root,
|
||||||
|
candidate_crosslink.data_root))):
|
||||||
|
winning_crosslink = candidate_crosslink
|
||||||
|
winning_crosslink_balance = crosslink_balance
|
||||||
|
|
||||||
|
let winning_attestations =
|
||||||
|
filterIt(attestations, it.data.crosslink == winning_crosslink)
|
||||||
|
(winning_crosslink,
|
||||||
|
get_unslashed_attesting_indices(state, winning_attestations, stateCache))
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#justification-and-finalization
|
||||||
|
func process_justification_and_finalization(
|
||||||
|
state: var BeaconState, stateCache: var StateCache) =
|
||||||
|
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
let
|
||||||
|
previous_epoch = get_previous_epoch(state)
|
||||||
|
current_epoch = get_current_epoch(state)
|
||||||
|
old_previous_justified_epoch = state.previous_justified_epoch
|
||||||
|
old_current_justified_epoch = state.current_justified_epoch
|
||||||
|
|
||||||
|
# Process justifications
|
||||||
|
state.previous_justified_epoch = state.current_justified_epoch
|
||||||
|
state.previous_justified_root = state.current_justified_root
|
||||||
|
state.justification_bitfield = (state.justification_bitfield shl 1)
|
||||||
|
let previous_epoch_matching_target_balance =
|
||||||
|
get_attesting_balance(state,
|
||||||
|
get_matching_target_attestations(state, previous_epoch), stateCache)
|
||||||
|
if previous_epoch_matching_target_balance * 3 >=
|
||||||
|
get_total_active_balance(state) * 2:
|
||||||
|
state.current_justified_epoch = previous_epoch
|
||||||
|
state.current_justified_root =
|
||||||
|
get_block_root(state, state.current_justified_epoch)
|
||||||
|
state.justification_bitfield = state.justification_bitfield or (1 shl 1)
|
||||||
|
let current_epoch_matching_target_balance =
|
||||||
|
get_attesting_balance(state,
|
||||||
|
get_matching_target_attestations(state, current_epoch),
|
||||||
|
stateCache)
|
||||||
|
if current_epoch_matching_target_balance * 3 >=
|
||||||
|
get_total_active_balance(state) * 2:
|
||||||
|
state.current_justified_epoch = current_epoch
|
||||||
|
state.current_justified_root =
|
||||||
|
get_block_root(state, state.current_justified_epoch)
|
||||||
|
state.justification_bitfield = state.justification_bitfield or (1 shl 0)
|
||||||
|
|
||||||
|
# Process finalizations
|
||||||
|
let bitfield = state.justification_bitfield
|
||||||
|
|
||||||
|
## The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th
|
||||||
|
## as source
|
||||||
|
if (bitfield shr 1) mod 8 == 0b111 and old_previous_justified_epoch + 3 ==
|
||||||
|
current_epoch:
|
||||||
|
state.finalized_epoch = old_previous_justified_epoch
|
||||||
|
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||||
|
|
||||||
|
## The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as
|
||||||
|
## source
|
||||||
|
if (bitfield shr 1) mod 4 == 0b11 and old_previous_justified_epoch + 2 ==
|
||||||
|
current_epoch:
|
||||||
|
state.finalized_epoch = old_previous_justified_epoch
|
||||||
|
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||||
|
|
||||||
|
## The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as
|
||||||
|
## source
|
||||||
|
if (bitfield shr 0) mod 8 == 0b111 and old_current_justified_epoch + 2 ==
|
||||||
|
current_epoch:
|
||||||
|
state.finalized_epoch = old_current_justified_epoch
|
||||||
|
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||||
|
|
||||||
|
## The 1st/2nd most recent epochs are justified, the 1st using the 2nd as
|
||||||
|
## source
|
||||||
|
if (bitfield shr 0) mod 4 == 0b11 and old_current_justified_epoch + 1 ==
|
||||||
|
current_epoch:
|
||||||
|
state.finalized_epoch = old_current_justified_epoch
|
||||||
|
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#crosslinks
|
||||||
|
func process_crosslinks(state: var BeaconState, stateCache: var StateCache) =
|
||||||
|
## TODO is there a semantic reason for this, or is this just a way to force
|
||||||
|
## copying? If so, why not just `list(foo)` or similar? This is strange. In
|
||||||
|
## this case, for type reasons, don't do weird
|
||||||
|
## [c for c in state.current_crosslinks] from spec.
|
||||||
|
state.previous_crosslinks = state.current_crosslinks
|
||||||
|
|
||||||
|
for epoch_int in get_previous_epoch(state).uint64 ..
|
||||||
|
get_current_epoch(state).uint64:
|
||||||
|
# This issue comes up regularly -- iterating means an int type,
|
||||||
|
# which then needs re-conversion back to specialized type.
|
||||||
|
let epoch = epoch_int.Epoch
|
||||||
|
for offset in 0'u64 ..< get_epoch_committee_count(state, epoch):
|
||||||
|
let
|
||||||
|
shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT
|
||||||
|
crosslink_committee =
|
||||||
|
get_crosslink_committee(state, epoch, shard, stateCache)
|
||||||
|
# In general, it'll loop over the same shards twice, and
|
||||||
|
# get_winning_root_and_participants is defined to return
|
||||||
|
# the same results from the previous epoch as current.
|
||||||
|
# TODO cache like before, in 0.5 version of this function
|
||||||
|
(winning_crosslink, attesting_indices) =
|
||||||
|
get_winning_crosslink_and_attesting_indices(
|
||||||
|
state, epoch, shard, stateCache)
|
||||||
|
if 3'u64 * get_total_balance(state, attesting_indices) >=
|
||||||
|
2'u64 * get_total_balance(state, crosslink_committee):
|
||||||
|
state.current_crosslinks[shard] = winning_crosslink
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
|
||||||
|
func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
|
||||||
|
let
|
||||||
|
total_balance = get_total_active_balance(state)
|
||||||
|
effective_balance = state.validator_registry[index].effective_balance
|
||||||
|
effective_balance * BASE_REWARD_FACTOR div
|
||||||
|
integer_squareroot(total_balance) div BASE_REWARDS_PER_EPOCH
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
|
||||||
|
func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
|
||||||
|
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
||||||
|
let
|
||||||
|
previous_epoch = get_previous_epoch(state)
|
||||||
|
total_balance = get_total_active_balance(state)
|
||||||
|
var
|
||||||
|
rewards = repeat(0'u64, len(state.validator_registry))
|
||||||
|
penalties = repeat(0'u64, len(state.validator_registry))
|
||||||
|
eligible_validator_indices : seq[ValidatorIndex] = @[]
|
||||||
|
|
||||||
|
for index, v in state.validator_registry:
|
||||||
|
if is_active_validator(v, previous_epoch) or
|
||||||
|
(v.slashed and previous_epoch + 1 < v.withdrawable_epoch):
|
||||||
|
eligible_validator_indices.add index.ValidatorIndex
|
||||||
|
|
||||||
|
# Micro-incentives for matching FFG source, FFG target, and head
|
||||||
|
let
|
||||||
|
matching_source_attestations =
|
||||||
|
get_matching_source_attestations(state, previous_epoch)
|
||||||
|
matching_target_attestations =
|
||||||
|
get_matching_target_attestations(state, previous_epoch)
|
||||||
|
matching_head_attestations =
|
||||||
|
get_matching_head_attestations(state, previous_epoch)
|
||||||
|
for attestations in
|
||||||
|
@[matching_source_attestations, matching_target_attestations,
|
||||||
|
matching_head_attestations]:
|
||||||
|
let
|
||||||
|
unslashed_attesting_indices =
|
||||||
|
get_unslashed_attesting_indices(state, attestations, stateCache)
|
||||||
|
attesting_balance = get_attesting_balance(state, attestations, stateCache)
|
||||||
|
for index in eligible_validator_indices:
|
||||||
|
if index in unslashed_attesting_indices:
|
||||||
|
rewards[index] +=
|
||||||
|
get_base_reward(state, index) * attesting_balance div total_balance
|
||||||
|
else:
|
||||||
|
penalties[index] += get_base_reward(state, index)
|
||||||
|
|
||||||
|
if matching_source_attestations.len == 0:
|
||||||
|
return (rewards, penalties)
|
||||||
|
|
||||||
|
# Proposer and inclusion delay micro-rewards
|
||||||
|
for index in get_unslashed_attesting_indices(
|
||||||
|
state, matching_source_attestations, stateCache):
|
||||||
|
doAssert matching_source_attestations.len > 0
|
||||||
|
var attestation = matching_source_attestations[0]
|
||||||
|
for a in matching_source_attestations:
|
||||||
|
if index notin get_attesting_indices(
|
||||||
|
state, a.data, a.aggregation_bitfield, stateCache):
|
||||||
|
continue
|
||||||
|
if a.inclusion_delay < attestation.inclusion_delay:
|
||||||
|
attestation = a
|
||||||
|
rewards[attestation.proposer_index] += get_base_reward(state, index) div
|
||||||
|
PROPOSER_REWARD_QUOTIENT
|
||||||
|
rewards[index] +=
|
||||||
|
get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY div
|
||||||
|
attestation.inclusion_delay
|
||||||
|
|
||||||
|
# Inactivity penalty
|
||||||
|
let finality_delay = previous_epoch - state.finalized_epoch
|
||||||
|
if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
||||||
|
let matching_target_attesting_indices =
|
||||||
|
get_unslashed_attesting_indices(
|
||||||
|
state, matching_target_attestations, stateCache)
|
||||||
|
for index in eligible_validator_indices:
|
||||||
|
penalties[index] +=
|
||||||
|
BASE_REWARDS_PER_EPOCH.uint64 * get_base_reward(state, index)
|
||||||
|
if index notin matching_target_attesting_indices:
|
||||||
|
penalties[index] +=
|
||||||
|
state.validator_registry[index].effective_balance *
|
||||||
|
finality_delay div INACTIVITY_PENALTY_QUOTIENT
|
||||||
|
|
||||||
|
(rewards, penalties)
|
||||||
|
|
||||||
|
# TODO re-cache this one, as under 0.5 version, if profiling suggests it
|
||||||
|
func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
|
||||||
|
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
||||||
|
|
||||||
|
var
|
||||||
|
rewards = repeat(0'u64, len(state.validator_registry))
|
||||||
|
penalties = repeat(0'u64, len(state.validator_registry))
|
||||||
|
let epoch = get_previous_epoch(state)
|
||||||
|
for offset in 0'u64 ..< get_epoch_committee_count(state, epoch):
|
||||||
|
let
|
||||||
|
shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT
|
||||||
|
crosslink_committee =
|
||||||
|
get_crosslink_committee(state, epoch, shard, cache)
|
||||||
|
(winning_crosslink, attesting_indices) =
|
||||||
|
get_winning_crosslink_and_attesting_indices(
|
||||||
|
state, epoch, shard, cache)
|
||||||
|
attesting_balance = get_total_balance(state, attesting_indices)
|
||||||
|
committee_balance = get_total_balance(state, crosslink_committee)
|
||||||
|
for index in crosslink_committee:
|
||||||
|
let base_reward = get_base_reward(state, index)
|
||||||
|
if index in attesting_indices:
|
||||||
|
rewards[index] +=
|
||||||
|
get_base_reward(state, index) * attesting_balance div
|
||||||
|
committee_balance
|
||||||
|
else:
|
||||||
|
penalties[index] += get_base_reward(state, index)
|
||||||
|
|
||||||
|
(rewards, penalties)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
|
||||||
|
func process_rewards_and_penalties(
|
||||||
|
state: var BeaconState, cache: var StateCache) =
|
||||||
|
if get_current_epoch(state) == GENESIS_EPOCH:
|
||||||
|
return
|
||||||
|
|
||||||
|
let
|
||||||
|
(rewards1, penalties1) = get_attestation_deltas(state, cache)
|
||||||
|
(rewards2, penalties2) = get_crosslink_deltas(state, cache)
|
||||||
|
for i in 0 ..< len(state.validator_registry):
|
||||||
|
increase_balance(state, i.ValidatorIndex, rewards1[i] + rewards2[i])
|
||||||
|
decrease_balance(state, i.ValidatorIndex, penalties1[i] + penalties2[i])
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#slashings
|
||||||
|
func process_slashings(state: var BeaconState) =
|
||||||
|
let
|
||||||
|
current_epoch = get_current_epoch(state)
|
||||||
|
total_balance = get_total_active_balance(state)
|
||||||
|
|
||||||
|
# Compute `total_penalties`
|
||||||
|
total_at_start = state.latest_slashed_balances[
|
||||||
|
(current_epoch + 1) mod LATEST_SLASHED_EXIT_LENGTH]
|
||||||
|
total_at_end =
|
||||||
|
state.latest_slashed_balances[current_epoch mod
|
||||||
|
LATEST_SLASHED_EXIT_LENGTH]
|
||||||
|
total_penalties = total_at_end - total_at_start
|
||||||
|
|
||||||
|
for index, validator in state.validator_registry:
|
||||||
|
if validator.slashed and current_epoch == validator.withdrawable_epoch -
|
||||||
|
LATEST_SLASHED_EXIT_LENGTH div 2:
|
||||||
|
let
|
||||||
|
penalty = max(
|
||||||
|
validator.effective_balance *
|
||||||
|
min(total_penalties * 3, total_balance) div total_balance,
|
||||||
|
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT)
|
||||||
|
decrease_balance(state, index.ValidatorIndex, penalty)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#final-updates
|
||||||
|
func process_final_updates(state: var BeaconState) =
|
||||||
|
let
|
||||||
|
current_epoch = get_current_epoch(state)
|
||||||
|
next_epoch = current_epoch + 1
|
||||||
|
|
||||||
|
# Reset eth1 data votes
|
||||||
|
if (state.slot + 1) mod SLOTS_PER_ETH1_VOTING_PERIOD == 0:
|
||||||
|
state.eth1_data_votes = @[]
|
||||||
|
|
||||||
|
# Update effective balances with hysteresis
|
||||||
|
for index, validator in state.validator_registry:
|
||||||
|
let balance = state.balances[index]
|
||||||
|
const HALF_INCREMENT = EFFECTIVE_BALANCE_INCREMENT div 2
|
||||||
|
if balance < validator.effective_balance or
|
||||||
|
validator.effective_balance + 3'u64 * HALF_INCREMENT < balance:
|
||||||
|
state.validator_registry[index].effective_balance =
|
||||||
|
min(
|
||||||
|
balance - balance mod EFFECTIVE_BALANCE_INCREMENT,
|
||||||
|
MAX_EFFECTIVE_BALANCE)
|
||||||
|
|
||||||
|
# Update start shard
|
||||||
|
state.latest_start_shard =
|
||||||
|
(state.latest_start_shard + get_shard_delta(state, current_epoch)) mod
|
||||||
|
SHARD_COUNT
|
||||||
|
|
||||||
|
# Set total slashed balances
|
||||||
|
state.latest_slashed_balances[next_epoch mod LATEST_SLASHED_EXIT_LENGTH] = (
|
||||||
|
state.latest_slashed_balances[current_epoch mod LATEST_SLASHED_EXIT_LENGTH]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set randao mix
|
||||||
|
state.latest_randao_mixes[next_epoch mod LATEST_RANDAO_MIXES_LENGTH] =
|
||||||
|
get_randao_mix(state, current_epoch)
|
||||||
|
|
||||||
|
# Set historical root accumulator
|
||||||
|
if next_epoch mod (SLOTS_PER_HISTORICAL_ROOT div SLOTS_PER_EPOCH).uint64 == 0:
|
||||||
|
let historical_batch = HistoricalBatch(
|
||||||
|
block_roots: state.latest_block_roots,
|
||||||
|
state_roots: state.latest_state_roots,
|
||||||
|
)
|
||||||
|
state.historical_roots.add (hash_tree_root(historical_batch))
|
||||||
|
|
||||||
|
# Rotate current/previous epoch attestations
|
||||||
|
state.previous_epoch_attestations = state.current_epoch_attestations
|
||||||
|
state.current_epoch_attestations = @[]
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#per-epoch-processing
|
||||||
|
func processEpoch*(state: var BeaconState) =
|
||||||
|
if not (state.slot > GENESIS_SLOT and
|
||||||
|
(state.slot + 1) mod SLOTS_PER_EPOCH == 0):
|
||||||
|
return
|
||||||
|
|
||||||
|
var per_epoch_cache = get_empty_per_epoch_cache()
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#justification-and-finalization
|
||||||
|
process_justification_and_finalization(state, per_epoch_cache)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#crosslinks
|
||||||
|
process_crosslinks(state, per_epoch_cache)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
|
||||||
|
process_rewards_and_penalties(state, per_epoch_cache)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#registry-updates
|
||||||
|
# Don't rely on caching here.
|
||||||
|
process_registry_updates(state)
|
||||||
|
|
||||||
|
## Caching here for get_crosslink_committee(...) can break otherwise, since
|
||||||
|
## get_active_validator_indices(...) usually changes.
|
||||||
|
clear(per_epoch_cache.crosslink_committee_cache)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#slashings
|
||||||
|
process_slashings(state)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#final-updates
|
||||||
|
process_final_updates(state)
|
|
@ -33,384 +33,8 @@
|
||||||
import
|
import
|
||||||
algorithm, collections/sets, chronicles, math, options, sequtils, sets, tables,
|
algorithm, collections/sets, chronicles, math, options, sequtils, sets, tables,
|
||||||
./extras, ./ssz, ./beacon_node_types,
|
./extras, ./ssz, ./beacon_node_types,
|
||||||
./spec/[beaconstate, bitfield, crypto, datatypes, digest, helpers, validator]
|
./spec/[beaconstate, bitfield, crypto, datatypes, digest, helpers, validator],
|
||||||
|
./spec/[state_transition_block, state_transition_epoch]
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#block-header
|
|
||||||
proc processBlockHeader(
|
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
|
||||||
stateCache: var StateCache): bool =
|
|
||||||
# Verify that the slots match
|
|
||||||
if not (blck.slot == state.slot):
|
|
||||||
notice "Block header: slot mismatch",
|
|
||||||
block_slot = humaneSlotNum(blck.slot),
|
|
||||||
state_slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify that the parent matches
|
|
||||||
if skipValidation notin flags and not (blck.parent_root ==
|
|
||||||
signing_root(state.latest_block_header)):
|
|
||||||
notice "Block header: previous block root mismatch",
|
|
||||||
latest_block_header = state.latest_block_header,
|
|
||||||
blck = shortLog(blck),
|
|
||||||
latest_block_header_root = shortLog(signing_root(state.latest_block_header))
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Save current block as the new latest block
|
|
||||||
state.latest_block_header = BeaconBlockHeader(
|
|
||||||
slot: blck.slot,
|
|
||||||
parent_root: blck.parent_root,
|
|
||||||
body_root: hash_tree_root(blck.body),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify proposer is not slashed
|
|
||||||
let proposer =
|
|
||||||
state.validator_registry[get_beacon_proposer_index(state, stateCache)]
|
|
||||||
if proposer.slashed:
|
|
||||||
notice "Block header: proposer slashed"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify proposer signature
|
|
||||||
if skipValidation notin flags and not bls_verify(
|
|
||||||
proposer.pubkey,
|
|
||||||
signing_root(blck).data,
|
|
||||||
blck.signature,
|
|
||||||
get_domain(state, DOMAIN_BEACON_PROPOSER)):
|
|
||||||
notice "Block header: invalid block header",
|
|
||||||
proposer_pubkey = proposer.pubkey,
|
|
||||||
block_root = shortLog(signing_root(blck)),
|
|
||||||
block_signature = blck.signature
|
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#randao
|
|
||||||
proc processRandao(
|
|
||||||
state: var BeaconState, body: BeaconBlockBody, flags: UpdateFlags,
|
|
||||||
stateCache: var StateCache): bool =
|
|
||||||
let
|
|
||||||
proposer_index = get_beacon_proposer_index(state, stateCache)
|
|
||||||
proposer = addr state.validator_registry[proposer_index]
|
|
||||||
|
|
||||||
# Verify that the provided randao value is valid
|
|
||||||
if skipValidation notin flags:
|
|
||||||
if not bls_verify(
|
|
||||||
proposer.pubkey,
|
|
||||||
hash_tree_root(get_current_epoch(state).uint64).data,
|
|
||||||
body.randao_reveal,
|
|
||||||
get_domain(state, DOMAIN_RANDAO)):
|
|
||||||
|
|
||||||
notice "Randao mismatch", proposer_pubkey = proposer.pubkey,
|
|
||||||
message = get_current_epoch(state),
|
|
||||||
signature = body.randao_reveal,
|
|
||||||
slot = state.slot
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Mix it in
|
|
||||||
let
|
|
||||||
mix = get_current_epoch(state) mod LATEST_RANDAO_MIXES_LENGTH
|
|
||||||
rr = eth2hash(body.randao_reveal.getBytes()).data
|
|
||||||
|
|
||||||
for i, b in state.latest_randao_mixes[mix].data:
|
|
||||||
state.latest_randao_mixes[mix].data[i] = b xor rr[i]
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#eth1-data
|
|
||||||
func processEth1Data(state: var BeaconState, body: BeaconBlockBody) =
|
|
||||||
state.eth1_data_votes.add body.eth1_data
|
|
||||||
if state.eth1_data_votes.count(body.eth1_data) * 2 >
|
|
||||||
SLOTS_PER_ETH1_VOTING_PERIOD:
|
|
||||||
state.latest_eth1_data = body.eth1_data
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#is_slashable_validator
|
|
||||||
func is_slashable_validator(validator: Validator, epoch: Epoch): bool =
|
|
||||||
# Check if ``validator`` is slashable.
|
|
||||||
(not validator.slashed) and
|
|
||||||
(validator.activation_epoch <= epoch) and
|
|
||||||
(epoch < validator.withdrawable_epoch)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#proposer-slashings
|
|
||||||
proc processProposerSlashings(
|
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
|
||||||
stateCache: var StateCache): bool =
|
|
||||||
if len(blck.body.proposer_slashings) > MAX_PROPOSER_SLASHINGS:
|
|
||||||
notice "PropSlash: too many!",
|
|
||||||
proposer_slashings = len(blck.body.proposer_slashings)
|
|
||||||
return false
|
|
||||||
|
|
||||||
for proposer_slashing in blck.body.proposer_slashings:
|
|
||||||
let proposer = state.validator_registry[proposer_slashing.proposer_index.int]
|
|
||||||
|
|
||||||
# Verify that the epoch is the same
|
|
||||||
if not (slot_to_epoch(proposer_slashing.header_1.slot) ==
|
|
||||||
slot_to_epoch(proposer_slashing.header_2.slot)):
|
|
||||||
notice "PropSlash: epoch mismatch"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# But the headers are different
|
|
||||||
if not (proposer_slashing.header_1 != proposer_slashing.header_2):
|
|
||||||
notice "PropSlash: headers not different"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Check proposer is slashable
|
|
||||||
if not is_slashable_validator(proposer, get_current_epoch(state)):
|
|
||||||
notice "PropSlash: slashed proposer"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Signatures are valid
|
|
||||||
if skipValidation notin flags:
|
|
||||||
for i, header in @[proposer_slashing.header_1, proposer_slashing.header_2]:
|
|
||||||
if not bls_verify(
|
|
||||||
proposer.pubkey,
|
|
||||||
signing_root(header).data,
|
|
||||||
header.signature,
|
|
||||||
get_domain(
|
|
||||||
state, DOMAIN_BEACON_PROPOSER, slot_to_epoch(header.slot))):
|
|
||||||
notice "PropSlash: invalid signature",
|
|
||||||
signature_index = i
|
|
||||||
return false
|
|
||||||
|
|
||||||
slashValidator(
|
|
||||||
state, proposer_slashing.proposer_index.ValidatorIndex, stateCache)
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#is_slashable_attestation_data
|
|
||||||
func is_slashable_attestation_data(
|
|
||||||
data_1: AttestationData, data_2: AttestationData): bool =
|
|
||||||
## Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG
|
|
||||||
## rules.
|
|
||||||
|
|
||||||
# Double vote
|
|
||||||
(data_1 != data_2 and data_1.target_epoch == data_2.target_epoch) or
|
|
||||||
# Surround vote
|
|
||||||
(data_1.source_epoch < data_2.source_epoch and
|
|
||||||
data_2.target_epoch < data_1.target_epoch)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#attester-slashings
|
|
||||||
proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock,
|
|
||||||
stateCache: var StateCache): bool =
|
|
||||||
# Process ``AttesterSlashing`` operation.
|
|
||||||
if len(blck.body.attester_slashings) > MAX_ATTESTER_SLASHINGS:
|
|
||||||
notice "CaspSlash: too many!"
|
|
||||||
return false
|
|
||||||
|
|
||||||
result = true
|
|
||||||
for attester_slashing in blck.body.attester_slashings:
|
|
||||||
let
|
|
||||||
attestation_1 = attester_slashing.attestation_1
|
|
||||||
attestation_2 = attester_slashing.attestation_2
|
|
||||||
|
|
||||||
if not is_slashable_attestation_data(
|
|
||||||
attestation_1.data, attestation_2.data):
|
|
||||||
notice "CaspSlash: surround or double vote check failed"
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not validate_indexed_attestation(state, attestation_1):
|
|
||||||
notice "CaspSlash: invalid votes 1"
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not validate_indexed_attestation(state, attestation_2):
|
|
||||||
notice "CaspSlash: invalid votes 2"
|
|
||||||
return false
|
|
||||||
|
|
||||||
var slashed_any = false
|
|
||||||
|
|
||||||
## TODO there's a lot of sorting/set construction here and
|
|
||||||
## verify_indexed_attestation, but go by spec unless there
|
|
||||||
## is compelling perf evidence otherwise.
|
|
||||||
let attesting_indices_1 =
|
|
||||||
attestation_1.custody_bit_0_indices & attestation_1.custody_bit_1_indices
|
|
||||||
let attesting_indices_2 =
|
|
||||||
attestation_2.custody_bit_0_indices & attestation_2.custody_bit_1_indices
|
|
||||||
for index in sorted(toSeq(intersection(toSet(attesting_indices_1),
|
|
||||||
toSet(attesting_indices_2)).items), system.cmp):
|
|
||||||
if is_slashable_validator(state.validator_registry[index.int],
|
|
||||||
get_current_epoch(state)):
|
|
||||||
slash_validator(state, index.ValidatorIndex, stateCache)
|
|
||||||
slashed_any = true
|
|
||||||
result = result and slashed_any
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestations
|
|
||||||
proc processAttestations(
|
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
|
||||||
stateCache: var StateCache): bool =
|
|
||||||
## Each block includes a number of attestations that the proposer chose. Each
|
|
||||||
## attestation represents an update to a specific shard and is signed by a
|
|
||||||
## committee of validators.
|
|
||||||
## Here we make sanity checks for each attestation and it to the state - most
|
|
||||||
## updates will happen at the epoch boundary where state updates happen in
|
|
||||||
## bulk.
|
|
||||||
if blck.body.attestations.len > MAX_ATTESTATIONS:
|
|
||||||
notice "Attestation: too many!", attestations = blck.body.attestations.len
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not blck.body.attestations.allIt(checkAttestation(state, it, flags, stateCache)):
|
|
||||||
return false
|
|
||||||
|
|
||||||
# All checks passed - update state
|
|
||||||
# Apply the attestations
|
|
||||||
var committee_count_cache = initTable[Epoch, uint64]()
|
|
||||||
|
|
||||||
for attestation in blck.body.attestations:
|
|
||||||
# Caching
|
|
||||||
let
|
|
||||||
epoch = attestation.data.target_epoch
|
|
||||||
committee_count = if epoch in committee_count_cache:
|
|
||||||
committee_count_cache[epoch]
|
|
||||||
else:
|
|
||||||
get_epoch_committee_count(state, epoch)
|
|
||||||
committee_count_cache[epoch] = committee_count
|
|
||||||
|
|
||||||
# Spec content
|
|
||||||
let attestation_slot =
|
|
||||||
get_attestation_data_slot(state, attestation.data, committee_count)
|
|
||||||
let pending_attestation = PendingAttestation(
|
|
||||||
data: attestation.data,
|
|
||||||
aggregation_bitfield: attestation.aggregation_bitfield,
|
|
||||||
inclusion_delay: state.slot - attestation_slot,
|
|
||||||
proposer_index: get_beacon_proposer_index(state, stateCache),
|
|
||||||
)
|
|
||||||
|
|
||||||
if attestation.data.target_epoch == get_current_epoch(state):
|
|
||||||
state.current_epoch_attestations.add(pending_attestation)
|
|
||||||
else:
|
|
||||||
state.previous_epoch_attestations.add(pending_attestation)
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#deposits
|
|
||||||
proc processDeposits(state: var BeaconState, blck: BeaconBlock): bool =
|
|
||||||
if not (len(blck.body.deposits) <= MAX_DEPOSITS):
|
|
||||||
notice "processDeposits: too many deposits"
|
|
||||||
return false
|
|
||||||
|
|
||||||
for deposit in blck.body.deposits:
|
|
||||||
if not process_deposit(state, deposit):
|
|
||||||
notice "processDeposits: deposit invalid"
|
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#voluntary-exits
|
|
||||||
proc processVoluntaryExits(
|
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
|
||||||
# Process ``VoluntaryExit`` transaction.
|
|
||||||
if len(blck.body.voluntary_exits) > MAX_VOLUNTARY_EXITS:
|
|
||||||
notice "Exit: too many!"
|
|
||||||
return false
|
|
||||||
|
|
||||||
for exit in blck.body.voluntary_exits:
|
|
||||||
let validator = state.validator_registry[exit.validator_index.int]
|
|
||||||
|
|
||||||
# Verify the validator is active
|
|
||||||
if not is_active_validator(validator, get_current_epoch(state)):
|
|
||||||
notice "Exit: validator not active"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify the validator has not yet exited
|
|
||||||
if not (validator.exit_epoch == FAR_FUTURE_EPOCH):
|
|
||||||
notice "Exit: validator has exited"
|
|
||||||
return false
|
|
||||||
|
|
||||||
## Exits must specify an epoch when they become valid; they are not valid
|
|
||||||
## before then
|
|
||||||
if not (get_current_epoch(state) >= exit.epoch):
|
|
||||||
notice "Exit: exit epoch not passed"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify the validator has been active long enough
|
|
||||||
# TODO detect underflow
|
|
||||||
if not (get_current_epoch(state) - validator.activation_epoch >=
|
|
||||||
PERSISTENT_COMMITTEE_PERIOD):
|
|
||||||
notice "Exit: not in validator set long enough"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify signature
|
|
||||||
if skipValidation notin flags:
|
|
||||||
if not bls_verify(
|
|
||||||
validator.pubkey, signing_root(exit).data, exit.signature,
|
|
||||||
get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch)):
|
|
||||||
notice "Exit: invalid signature"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Initiate exit
|
|
||||||
initiate_validator_exit(state, exit.validator_index.ValidatorIndex)
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#transfers
|
|
||||||
proc processTransfers(state: var BeaconState, blck: BeaconBlock,
|
|
||||||
flags: UpdateFlags, stateCache: var StateCache): bool =
|
|
||||||
if not (len(blck.body.transfers) <= MAX_TRANSFERS):
|
|
||||||
notice "Transfer: too many transfers"
|
|
||||||
return false
|
|
||||||
|
|
||||||
for transfer in blck.body.transfers:
|
|
||||||
let sender_balance = state.balances[transfer.sender.int]
|
|
||||||
|
|
||||||
## Verify the amount and fee are not individually too big (for anti-overflow
|
|
||||||
## purposes)
|
|
||||||
if not (sender_balance >= max(transfer.amount, transfer.fee)):
|
|
||||||
notice "Transfer: sender balance too low for transfer amount or fee"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# A transfer is valid in only one slot
|
|
||||||
if not (state.slot == transfer.slot):
|
|
||||||
notice "Transfer: slot mismatch"
|
|
||||||
return false
|
|
||||||
|
|
||||||
## Sender must be not yet eligible for activation, withdrawn, or transfer
|
|
||||||
## balance over MAX_EFFECTIVE_BALANCE
|
|
||||||
if not (
|
|
||||||
state.validator_registry[transfer.sender.int].activation_epoch ==
|
|
||||||
FAR_FUTURE_EPOCH or
|
|
||||||
get_current_epoch(state) >=
|
|
||||||
state.validator_registry[
|
|
||||||
transfer.sender.int].withdrawable_epoch or
|
|
||||||
transfer.amount + transfer.fee + MAX_EFFECTIVE_BALANCE <=
|
|
||||||
state.balances[transfer.sender.int]):
|
|
||||||
notice "Transfer: only withdrawn or not-activated accounts with sufficient balance can transfer"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify that the pubkey is valid
|
|
||||||
let wc = state.validator_registry[transfer.sender.int].
|
|
||||||
withdrawal_credentials
|
|
||||||
if not (wc.data[0] == BLS_WITHDRAWAL_PREFIX and
|
|
||||||
wc.data[1..^1] == eth2hash(transfer.pubkey.getBytes).data[1..^1]):
|
|
||||||
notice "Transfer: incorrect withdrawal credentials"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Verify that the signature is valid
|
|
||||||
if skipValidation notin flags:
|
|
||||||
if not bls_verify(
|
|
||||||
transfer.pubkey, signing_root(transfer).data, transfer.signature,
|
|
||||||
get_domain(state, DOMAIN_TRANSFER)):
|
|
||||||
notice "Transfer: incorrect signature"
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Process the transfer
|
|
||||||
decrease_balance(
|
|
||||||
state, transfer.sender.ValidatorIndex, transfer.amount + transfer.fee)
|
|
||||||
increase_balance(
|
|
||||||
state, transfer.recipient.ValidatorIndex, transfer.amount)
|
|
||||||
increase_balance(
|
|
||||||
state, get_beacon_proposer_index(state, stateCache), transfer.fee)
|
|
||||||
|
|
||||||
# Verify balances are not dust
|
|
||||||
if not (
|
|
||||||
0'u64 < state.balances[transfer.sender.int] and
|
|
||||||
state.balances[transfer.sender.int] < MIN_DEPOSIT_AMOUNT):
|
|
||||||
notice "Transfer: sender balance too low for transfer amount or fee"
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not (
|
|
||||||
0'u64 < state.balances[transfer.recipient.int] and
|
|
||||||
state.balances[transfer.recipient.int] < MIN_DEPOSIT_AMOUNT):
|
|
||||||
notice "Transfer: sender balance too low for transfer amount or fee"
|
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#per-slot-processing
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#per-slot-processing
|
||||||
func advance_slot(state: var BeaconState) =
|
func advance_slot(state: var BeaconState) =
|
||||||
|
@ -436,475 +60,6 @@ func process_slot(state: var BeaconState) =
|
||||||
state.latest_block_roots[state.slot mod SLOTS_PER_HISTORICAL_ROOT] =
|
state.latest_block_roots[state.slot mod SLOTS_PER_HISTORICAL_ROOT] =
|
||||||
signing_root(state.latest_block_header)
|
signing_root(state.latest_block_header)
|
||||||
|
|
||||||
proc processBlock(
|
|
||||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags,
|
|
||||||
stateCache: var StateCache): bool =
|
|
||||||
## When there's a new block, we need to verify that the block is sane and
|
|
||||||
## update the state accordingly
|
|
||||||
|
|
||||||
# TODO when there's a failure, we should reset the state!
|
|
||||||
# TODO probably better to do all verification first, then apply state changes
|
|
||||||
|
|
||||||
if not processBlockHeader(state, blck, flags, stateCache):
|
|
||||||
notice "Block header not valid", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not processRandao(state, blck.body, flags, stateCache):
|
|
||||||
debug "[Block processing] Randao failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
processEth1Data(state, blck.body)
|
|
||||||
|
|
||||||
if not processProposerSlashings(state, blck, flags, stateCache):
|
|
||||||
debug "[Block processing] Proposer slashing failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not processAttesterSlashings(state, blck, stateCache):
|
|
||||||
debug "[Block processing] Attester slashing failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not processAttestations(state, blck, flags, stateCache):
|
|
||||||
debug "[Block processing] Attestation processing failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not processDeposits(state, blck):
|
|
||||||
debug "[Block processing] Deposit processing failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not processVoluntaryExits(state, blck, flags):
|
|
||||||
debug "[Block processing] Exit processing failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if not processTransfers(state, blck, flags, stateCache):
|
|
||||||
debug "[Block processing] Transfer processing failure", slot = humaneSlotNum(state.slot)
|
|
||||||
return false
|
|
||||||
|
|
||||||
true
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#helper-functions-1
|
|
||||||
func get_total_active_balance(state: BeaconState): Gwei =
|
|
||||||
return get_total_balance(
|
|
||||||
state,
|
|
||||||
get_active_validator_indices(state, get_current_epoch(state)))
|
|
||||||
|
|
||||||
func get_matching_source_attestations(state: BeaconState, epoch: Epoch):
|
|
||||||
seq[PendingAttestation] =
|
|
||||||
doAssert epoch in @[get_current_epoch(state), get_previous_epoch(state)]
|
|
||||||
if epoch == get_current_epoch(state):
|
|
||||||
state.current_epoch_attestations
|
|
||||||
else:
|
|
||||||
state.previous_epoch_attestations
|
|
||||||
|
|
||||||
func get_matching_target_attestations(state: BeaconState, epoch: Epoch):
|
|
||||||
seq[PendingAttestation] =
|
|
||||||
filterIt(
|
|
||||||
get_matching_source_attestations(state, epoch),
|
|
||||||
it.data.target_root == get_block_root(state, epoch)
|
|
||||||
)
|
|
||||||
|
|
||||||
func get_matching_head_attestations(state: BeaconState, epoch: Epoch):
|
|
||||||
seq[PendingAttestation] =
|
|
||||||
filterIt(
|
|
||||||
get_matching_source_attestations(state, epoch),
|
|
||||||
it.data.beacon_block_root ==
|
|
||||||
get_block_root_at_slot(state, get_attestation_data_slot(state, it.data))
|
|
||||||
)
|
|
||||||
|
|
||||||
func get_unslashed_attesting_indices(
|
|
||||||
state: BeaconState, attestations: seq[PendingAttestation],
|
|
||||||
stateCache: var StateCache): HashSet[ValidatorIndex] =
|
|
||||||
result = initSet[ValidatorIndex]()
|
|
||||||
for a in attestations:
|
|
||||||
result = result.union(get_attesting_indices(
|
|
||||||
state, a.data, a.aggregation_bitfield, stateCache))
|
|
||||||
|
|
||||||
for index in result:
|
|
||||||
if state.validator_registry[index].slashed:
|
|
||||||
result.excl index
|
|
||||||
|
|
||||||
func get_attesting_balance(
|
|
||||||
state: BeaconState, attestations: seq[PendingAttestation],
|
|
||||||
stateCache: var StateCache): Gwei =
|
|
||||||
get_total_balance(state, get_unslashed_attesting_indices(
|
|
||||||
state, attestations, stateCache))
|
|
||||||
|
|
||||||
# Not exactly in spec, but for get_winning_crosslink_and_attesting_indices
|
|
||||||
func lowerThan(candidate, current: Eth2Digest): bool =
|
|
||||||
# return true iff candidate is "lower" than current, per spec rule:
|
|
||||||
# "ties broken in favor of lexicographically higher hash
|
|
||||||
for i, v in current.data:
|
|
||||||
if v > candidate.data[i]: return true
|
|
||||||
false
|
|
||||||
|
|
||||||
func get_winning_crosslink_and_attesting_indices(
|
|
||||||
state: BeaconState, epoch: Epoch, shard: Shard,
|
|
||||||
stateCache: var StateCache): tuple[a: Crosslink, b: HashSet[ValidatorIndex]] =
|
|
||||||
let
|
|
||||||
attestations =
|
|
||||||
filterIt(
|
|
||||||
get_matching_source_attestations(state, epoch),
|
|
||||||
it.data.crosslink.shard == shard)
|
|
||||||
# TODO don't keep h_t_r'ing state.current_crosslinks[shard]
|
|
||||||
crosslinks =
|
|
||||||
filterIt(
|
|
||||||
mapIt(attestations, it.data.crosslink),
|
|
||||||
hash_tree_root(state.current_crosslinks[shard]) in
|
|
||||||
# TODO pointless memory allocation, etc.
|
|
||||||
@[it.parent_root, hash_tree_root(it)])
|
|
||||||
|
|
||||||
# default=Crosslink()
|
|
||||||
if len(crosslinks) == 0:
|
|
||||||
return (Crosslink(), initSet[ValidatorIndex]())
|
|
||||||
|
|
||||||
## Winning crosslink has the crosslink data root with the most balance voting
|
|
||||||
## for it (ties broken lexicographically)
|
|
||||||
var
|
|
||||||
winning_crosslink: Crosslink
|
|
||||||
winning_crosslink_balance = 0.Gwei
|
|
||||||
|
|
||||||
for candidate_crosslink in crosslinks:
|
|
||||||
## TODO check if should cache this again
|
|
||||||
## let root_balance = get_attesting_balance_cached(
|
|
||||||
## state, attestations_for.getOrDefault(r), cache)
|
|
||||||
let crosslink_balance =
|
|
||||||
get_attesting_balance(
|
|
||||||
state,
|
|
||||||
filterIt(attestations, it.data.crosslink == candidate_crosslink),
|
|
||||||
stateCache)
|
|
||||||
if (crosslink_balance > winning_crosslink_balance or
|
|
||||||
(winning_crosslink_balance == crosslink_balance and
|
|
||||||
lowerThan(winning_crosslink.data_root,
|
|
||||||
candidate_crosslink.data_root))):
|
|
||||||
winning_crosslink = candidate_crosslink
|
|
||||||
winning_crosslink_balance = crosslink_balance
|
|
||||||
|
|
||||||
let winning_attestations =
|
|
||||||
filterIt(attestations, it.data.crosslink == winning_crosslink)
|
|
||||||
(winning_crosslink,
|
|
||||||
get_unslashed_attesting_indices(state, winning_attestations, stateCache))
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#justification-and-finalization
|
|
||||||
func process_justification_and_finalization(
|
|
||||||
state: var BeaconState, stateCache: var StateCache) =
|
|
||||||
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
|
|
||||||
return
|
|
||||||
|
|
||||||
let
|
|
||||||
previous_epoch = get_previous_epoch(state)
|
|
||||||
current_epoch = get_current_epoch(state)
|
|
||||||
old_previous_justified_epoch = state.previous_justified_epoch
|
|
||||||
old_current_justified_epoch = state.current_justified_epoch
|
|
||||||
|
|
||||||
# Process justifications
|
|
||||||
state.previous_justified_epoch = state.current_justified_epoch
|
|
||||||
state.previous_justified_root = state.current_justified_root
|
|
||||||
state.justification_bitfield = (state.justification_bitfield shl 1)
|
|
||||||
let previous_epoch_matching_target_balance =
|
|
||||||
get_attesting_balance(state,
|
|
||||||
get_matching_target_attestations(state, previous_epoch), stateCache)
|
|
||||||
if previous_epoch_matching_target_balance * 3 >=
|
|
||||||
get_total_active_balance(state) * 2:
|
|
||||||
state.current_justified_epoch = previous_epoch
|
|
||||||
state.current_justified_root =
|
|
||||||
get_block_root(state, state.current_justified_epoch)
|
|
||||||
state.justification_bitfield = state.justification_bitfield or (1 shl 1)
|
|
||||||
let current_epoch_matching_target_balance =
|
|
||||||
get_attesting_balance(state,
|
|
||||||
get_matching_target_attestations(state, current_epoch),
|
|
||||||
stateCache)
|
|
||||||
if current_epoch_matching_target_balance * 3 >=
|
|
||||||
get_total_active_balance(state) * 2:
|
|
||||||
state.current_justified_epoch = current_epoch
|
|
||||||
state.current_justified_root =
|
|
||||||
get_block_root(state, state.current_justified_epoch)
|
|
||||||
state.justification_bitfield = state.justification_bitfield or (1 shl 0)
|
|
||||||
|
|
||||||
# Process finalizations
|
|
||||||
let bitfield = state.justification_bitfield
|
|
||||||
|
|
||||||
## The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th
|
|
||||||
## as source
|
|
||||||
if (bitfield shr 1) mod 8 == 0b111 and old_previous_justified_epoch + 3 ==
|
|
||||||
current_epoch:
|
|
||||||
state.finalized_epoch = old_previous_justified_epoch
|
|
||||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
|
||||||
|
|
||||||
## The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as
|
|
||||||
## source
|
|
||||||
if (bitfield shr 1) mod 4 == 0b11 and old_previous_justified_epoch + 2 ==
|
|
||||||
current_epoch:
|
|
||||||
state.finalized_epoch = old_previous_justified_epoch
|
|
||||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
|
||||||
|
|
||||||
## The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as
|
|
||||||
## source
|
|
||||||
if (bitfield shr 0) mod 8 == 0b111 and old_current_justified_epoch + 2 ==
|
|
||||||
current_epoch:
|
|
||||||
state.finalized_epoch = old_current_justified_epoch
|
|
||||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
|
||||||
|
|
||||||
## The 1st/2nd most recent epochs are justified, the 1st using the 2nd as
|
|
||||||
## source
|
|
||||||
if (bitfield shr 0) mod 4 == 0b11 and old_current_justified_epoch + 1 ==
|
|
||||||
current_epoch:
|
|
||||||
state.finalized_epoch = old_current_justified_epoch
|
|
||||||
state.finalized_root = get_block_root(state, state.finalized_epoch)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#crosslinks
|
|
||||||
func process_crosslinks(state: var BeaconState, stateCache: var StateCache) =
|
|
||||||
## TODO is there a semantic reason for this, or is this just a way to force
|
|
||||||
## copying? If so, why not just `list(foo)` or similar? This is strange. In
|
|
||||||
## this case, for type reasons, don't do weird
|
|
||||||
## [c for c in state.current_crosslinks] from spec.
|
|
||||||
state.previous_crosslinks = state.current_crosslinks
|
|
||||||
|
|
||||||
for epoch_int in get_previous_epoch(state).uint64 ..
|
|
||||||
get_current_epoch(state).uint64:
|
|
||||||
# This issue comes up regularly -- iterating means an int type,
|
|
||||||
# which then needs re-conversion back to specialized type.
|
|
||||||
let epoch = epoch_int.Epoch
|
|
||||||
for offset in 0'u64 ..< get_epoch_committee_count(state, epoch):
|
|
||||||
let
|
|
||||||
shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT
|
|
||||||
crosslink_committee =
|
|
||||||
get_crosslink_committee(state, epoch, shard, stateCache)
|
|
||||||
# In general, it'll loop over the same shards twice, and
|
|
||||||
# get_winning_root_and_participants is defined to return
|
|
||||||
# the same results from the previous epoch as current.
|
|
||||||
# TODO cache like before, in 0.5 version of this function
|
|
||||||
(winning_crosslink, attesting_indices) =
|
|
||||||
get_winning_crosslink_and_attesting_indices(
|
|
||||||
state, epoch, shard, stateCache)
|
|
||||||
if 3'u64 * get_total_balance(state, attesting_indices) >=
|
|
||||||
2'u64 * get_total_balance(state, crosslink_committee):
|
|
||||||
state.current_crosslinks[shard] = winning_crosslink
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
|
|
||||||
func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
|
|
||||||
let
|
|
||||||
total_balance = get_total_active_balance(state)
|
|
||||||
effective_balance = state.validator_registry[index].effective_balance
|
|
||||||
effective_balance * BASE_REWARD_FACTOR div
|
|
||||||
integer_squareroot(total_balance) div BASE_REWARDS_PER_EPOCH
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#rewards-and-penalties
|
|
||||||
func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
|
|
||||||
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
|
||||||
let
|
|
||||||
previous_epoch = get_previous_epoch(state)
|
|
||||||
total_balance = get_total_active_balance(state)
|
|
||||||
var
|
|
||||||
rewards = repeat(0'u64, len(state.validator_registry))
|
|
||||||
penalties = repeat(0'u64, len(state.validator_registry))
|
|
||||||
eligible_validator_indices : seq[ValidatorIndex] = @[]
|
|
||||||
|
|
||||||
for index, v in state.validator_registry:
|
|
||||||
if is_active_validator(v, previous_epoch) or
|
|
||||||
(v.slashed and previous_epoch + 1 < v.withdrawable_epoch):
|
|
||||||
eligible_validator_indices.add index.ValidatorIndex
|
|
||||||
|
|
||||||
# Micro-incentives for matching FFG source, FFG target, and head
|
|
||||||
let
|
|
||||||
matching_source_attestations =
|
|
||||||
get_matching_source_attestations(state, previous_epoch)
|
|
||||||
matching_target_attestations =
|
|
||||||
get_matching_target_attestations(state, previous_epoch)
|
|
||||||
matching_head_attestations =
|
|
||||||
get_matching_head_attestations(state, previous_epoch)
|
|
||||||
for attestations in
|
|
||||||
@[matching_source_attestations, matching_target_attestations,
|
|
||||||
matching_head_attestations]:
|
|
||||||
let
|
|
||||||
unslashed_attesting_indices =
|
|
||||||
get_unslashed_attesting_indices(state, attestations, stateCache)
|
|
||||||
attesting_balance = get_attesting_balance(state, attestations, stateCache)
|
|
||||||
for index in eligible_validator_indices:
|
|
||||||
if index in unslashed_attesting_indices:
|
|
||||||
rewards[index] +=
|
|
||||||
get_base_reward(state, index) * attesting_balance div total_balance
|
|
||||||
else:
|
|
||||||
penalties[index] += get_base_reward(state, index)
|
|
||||||
|
|
||||||
if matching_source_attestations.len == 0:
|
|
||||||
return (rewards, penalties)
|
|
||||||
|
|
||||||
# Proposer and inclusion delay micro-rewards
|
|
||||||
for index in get_unslashed_attesting_indices(
|
|
||||||
state, matching_source_attestations, stateCache):
|
|
||||||
doAssert matching_source_attestations.len > 0
|
|
||||||
var attestation = matching_source_attestations[0]
|
|
||||||
for a in matching_source_attestations:
|
|
||||||
if index notin get_attesting_indices(
|
|
||||||
state, a.data, a.aggregation_bitfield, stateCache):
|
|
||||||
continue
|
|
||||||
if a.inclusion_delay < attestation.inclusion_delay:
|
|
||||||
attestation = a
|
|
||||||
rewards[attestation.proposer_index] += get_base_reward(state, index) div
|
|
||||||
PROPOSER_REWARD_QUOTIENT
|
|
||||||
rewards[index] +=
|
|
||||||
get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY div
|
|
||||||
attestation.inclusion_delay
|
|
||||||
|
|
||||||
# Inactivity penalty
|
|
||||||
let finality_delay = previous_epoch - state.finalized_epoch
|
|
||||||
if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
|
||||||
let matching_target_attesting_indices =
|
|
||||||
get_unslashed_attesting_indices(
|
|
||||||
state, matching_target_attestations, stateCache)
|
|
||||||
for index in eligible_validator_indices:
|
|
||||||
penalties[index] +=
|
|
||||||
BASE_REWARDS_PER_EPOCH.uint64 * get_base_reward(state, index)
|
|
||||||
if index notin matching_target_attesting_indices:
|
|
||||||
penalties[index] +=
|
|
||||||
state.validator_registry[index].effective_balance *
|
|
||||||
finality_delay div INACTIVITY_PENALTY_QUOTIENT
|
|
||||||
|
|
||||||
(rewards, penalties)
|
|
||||||
|
|
||||||
# TODO re-cache this one, as under 0.5 version, if profiling suggests it
|
|
||||||
func get_crosslink_deltas(state: BeaconState, cache: var StateCache):
|
|
||||||
tuple[a: seq[Gwei], b: seq[Gwei]] =
|
|
||||||
|
|
||||||
var
|
|
||||||
rewards = repeat(0'u64, len(state.validator_registry))
|
|
||||||
penalties = repeat(0'u64, len(state.validator_registry))
|
|
||||||
let epoch = get_previous_epoch(state)
|
|
||||||
for offset in 0'u64 ..< get_epoch_committee_count(state, epoch):
|
|
||||||
let
|
|
||||||
shard = (get_epoch_start_shard(state, epoch) + offset) mod SHARD_COUNT
|
|
||||||
crosslink_committee =
|
|
||||||
get_crosslink_committee(state, epoch, shard, cache)
|
|
||||||
(winning_crosslink, attesting_indices) =
|
|
||||||
get_winning_crosslink_and_attesting_indices(
|
|
||||||
state, epoch, shard, cache)
|
|
||||||
attesting_balance = get_total_balance(state, attesting_indices)
|
|
||||||
committee_balance = get_total_balance(state, crosslink_committee)
|
|
||||||
for index in crosslink_committee:
|
|
||||||
let base_reward = get_base_reward(state, index)
|
|
||||||
if index in attesting_indices:
|
|
||||||
rewards[index] +=
|
|
||||||
get_base_reward(state, index) * attesting_balance div
|
|
||||||
committee_balance
|
|
||||||
else:
|
|
||||||
penalties[index] += get_base_reward(state, index)
|
|
||||||
|
|
||||||
(rewards, penalties)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
|
|
||||||
func process_rewards_and_penalties(
|
|
||||||
state: var BeaconState, cache: var StateCache) =
|
|
||||||
if get_current_epoch(state) == GENESIS_EPOCH:
|
|
||||||
return
|
|
||||||
|
|
||||||
let
|
|
||||||
(rewards1, penalties1) = get_attestation_deltas(state, cache)
|
|
||||||
(rewards2, penalties2) = get_crosslink_deltas(state, cache)
|
|
||||||
for i in 0 ..< len(state.validator_registry):
|
|
||||||
increase_balance(state, i.ValidatorIndex, rewards1[i] + rewards2[i])
|
|
||||||
decrease_balance(state, i.ValidatorIndex, penalties1[i] + penalties2[i])
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#slashings
|
|
||||||
func process_slashings(state: var BeaconState) =
|
|
||||||
let
|
|
||||||
current_epoch = get_current_epoch(state)
|
|
||||||
total_balance = get_total_active_balance(state)
|
|
||||||
|
|
||||||
# Compute `total_penalties`
|
|
||||||
total_at_start = state.latest_slashed_balances[
|
|
||||||
(current_epoch + 1) mod LATEST_SLASHED_EXIT_LENGTH]
|
|
||||||
total_at_end =
|
|
||||||
state.latest_slashed_balances[current_epoch mod
|
|
||||||
LATEST_SLASHED_EXIT_LENGTH]
|
|
||||||
total_penalties = total_at_end - total_at_start
|
|
||||||
|
|
||||||
for index, validator in state.validator_registry:
|
|
||||||
if validator.slashed and current_epoch == validator.withdrawable_epoch -
|
|
||||||
LATEST_SLASHED_EXIT_LENGTH div 2:
|
|
||||||
let
|
|
||||||
penalty = max(
|
|
||||||
validator.effective_balance *
|
|
||||||
min(total_penalties * 3, total_balance) div total_balance,
|
|
||||||
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT)
|
|
||||||
decrease_balance(state, index.ValidatorIndex, penalty)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#final-updates
|
|
||||||
func process_final_updates(state: var BeaconState) =
|
|
||||||
let
|
|
||||||
current_epoch = get_current_epoch(state)
|
|
||||||
next_epoch = current_epoch + 1
|
|
||||||
|
|
||||||
# Reset eth1 data votes
|
|
||||||
if (state.slot + 1) mod SLOTS_PER_ETH1_VOTING_PERIOD == 0:
|
|
||||||
state.eth1_data_votes = @[]
|
|
||||||
|
|
||||||
# Update effective balances with hysteresis
|
|
||||||
for index, validator in state.validator_registry:
|
|
||||||
let balance = state.balances[index]
|
|
||||||
const HALF_INCREMENT = EFFECTIVE_BALANCE_INCREMENT div 2
|
|
||||||
if balance < validator.effective_balance or
|
|
||||||
validator.effective_balance + 3'u64 * HALF_INCREMENT < balance:
|
|
||||||
state.validator_registry[index].effective_balance =
|
|
||||||
min(
|
|
||||||
balance - balance mod EFFECTIVE_BALANCE_INCREMENT,
|
|
||||||
MAX_EFFECTIVE_BALANCE)
|
|
||||||
|
|
||||||
# Update start shard
|
|
||||||
state.latest_start_shard =
|
|
||||||
(state.latest_start_shard + get_shard_delta(state, current_epoch)) mod
|
|
||||||
SHARD_COUNT
|
|
||||||
|
|
||||||
# Set total slashed balances
|
|
||||||
state.latest_slashed_balances[next_epoch mod LATEST_SLASHED_EXIT_LENGTH] = (
|
|
||||||
state.latest_slashed_balances[current_epoch mod LATEST_SLASHED_EXIT_LENGTH]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set randao mix
|
|
||||||
state.latest_randao_mixes[next_epoch mod LATEST_RANDAO_MIXES_LENGTH] =
|
|
||||||
get_randao_mix(state, current_epoch)
|
|
||||||
|
|
||||||
# Set historical root accumulator
|
|
||||||
if next_epoch mod (SLOTS_PER_HISTORICAL_ROOT div SLOTS_PER_EPOCH).uint64 == 0:
|
|
||||||
let historical_batch = HistoricalBatch(
|
|
||||||
block_roots: state.latest_block_roots,
|
|
||||||
state_roots: state.latest_state_roots,
|
|
||||||
)
|
|
||||||
state.historical_roots.add (hash_tree_root(historical_batch))
|
|
||||||
|
|
||||||
# Rotate current/previous epoch attestations
|
|
||||||
state.previous_epoch_attestations = state.current_epoch_attestations
|
|
||||||
state.current_epoch_attestations = @[]
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#per-epoch-processing
|
|
||||||
func processEpoch(state: var BeaconState) =
|
|
||||||
if not (state.slot > GENESIS_SLOT and
|
|
||||||
(state.slot + 1) mod SLOTS_PER_EPOCH == 0):
|
|
||||||
return
|
|
||||||
|
|
||||||
var per_epoch_cache = get_empty_per_epoch_cache()
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#justification-and-finalization
|
|
||||||
process_justification_and_finalization(state, per_epoch_cache)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#crosslinks
|
|
||||||
process_crosslinks(state, per_epoch_cache)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
|
|
||||||
process_rewards_and_penalties(state, per_epoch_cache)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#registry-updates
|
|
||||||
# Don't rely on caching here.
|
|
||||||
process_registry_updates(state)
|
|
||||||
|
|
||||||
## Caching here for get_crosslink_committee(...) can break otherwise, since
|
|
||||||
## get_active_validator_indices(...) usually changes.
|
|
||||||
clear(per_epoch_cache.crosslink_committee_cache)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#slashings
|
|
||||||
process_slashings(state)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#final-updates
|
|
||||||
process_final_updates(state)
|
|
||||||
|
|
||||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#state-root-verification
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#state-root-verification
|
||||||
proc verifyStateRoot(state: BeaconState, blck: BeaconBlock): bool =
|
proc verifyStateRoot(state: BeaconState, blck: BeaconBlock): bool =
|
||||||
let state_root = hash_tree_root(state)
|
let state_root = hash_tree_root(state)
|
||||||
|
|
Loading…
Reference in New Issue