nimbus-eth2/beacon_chain/spec/beaconstate.nim

603 lines
24 KiB
Nim

# 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.
import
algorithm, chronicles, collections/sets, math, options, sequtils,
../extras, ../ssz, ../beacon_node_types,
./bitfield, ./crypto, ./datatypes, ./digest, ./helpers, ./validator,
tables
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#verify_merkle_branch
func verify_merkle_branch(leaf: Eth2Digest, proof: openarray[Eth2Digest], depth: uint64, index: uint64, root: Eth2Digest): bool =
## Verify that the given ``leaf`` is on the merkle branch ``proof``
## starting with the given ``root``.
var
value = leaf
buf: array[64, byte]
for i in 0 ..< depth.int:
if (index div (1'u64 shl i)) mod 2 != 0:
buf[0..31] = proof[i.int].data
buf[32..63] = value.data
else:
buf[0..31] = value.data
buf[32..63] = proof[i.int].data
value = eth2hash(buf)
value == root
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#increase_balance
func increase_balance*(
state: var BeaconState, index: ValidatorIndex, delta: Gwei) =
# Increase the validator balance at index ``index`` by ``delta``.
state.balances[index] += delta
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#decrease_balance
func decrease_balance*(
state: var BeaconState, index: ValidatorIndex, delta: Gwei) =
# Decrease validator balance by ``delta`` with underflow protection.
state.balances[index] =
if delta > state.balances[index]:
0'u64
else:
state.balances[index] - delta
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#deposits
func process_deposit*(
state: var BeaconState, deposit: Deposit, flags: UpdateFlags = {}): bool =
# Process an Eth1 deposit, registering a validator or increasing its balance.
# Verify the Merkle branch
# TODO enable this check, but don't use doAssert
if not verify_merkle_branch(
hash_tree_root(deposit.data),
deposit.proof,
DEPOSIT_CONTRACT_TREE_DEPTH,
state.deposit_index,
state.latest_eth1_data.deposit_root,
):
## TODO: a notice-like mechanism which works in a func
## here and elsewhere, one minimal approach is a check-if-true
## and return false iff so.
## obviously, better/more principled ones exist, but
## generally require broader rearchitecting, and this is what
## mostly happens now, just haphazardly.
discard
# Deposits must be processed in order
state.deposit_index += 1
let
pubkey = deposit.data.pubkey
amount = deposit.data.amount
validator_pubkeys = mapIt(state.validator_registry, it.pubkey)
index = validator_pubkeys.find(pubkey)
if index == -1:
# Verify the deposit signature (proof of possession)
# TODO should be get_domain(state, DOMAIN_DEPOSIT)
if skipValidation notin flags and not bls_verify(
pubkey, signing_root(deposit.data).data, deposit.data.signature,
3'u64):
return false
# Add validator and balance entries
state.validator_registry.add(Validator(
pubkey: pubkey,
withdrawal_credentials: deposit.data.withdrawal_credentials,
activation_eligibility_epoch: FAR_FUTURE_EPOCH,
activation_epoch: FAR_FUTURE_EPOCH,
exit_epoch: FAR_FUTURE_EPOCH,
withdrawable_epoch: FAR_FUTURE_EPOCH,
effective_balance: min(amount - amount mod EFFECTIVE_BALANCE_INCREMENT,
MAX_EFFECTIVE_BALANCE)
))
state.balances.add(amount)
else:
# Increase balance by deposit amount
increase_balance(state, index.ValidatorIndex, amount)
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#get_delayed_activation_exit_epoch
func get_delayed_activation_exit_epoch*(epoch: Epoch): Epoch =
## Return the epoch at which an activation or exit triggered in ``epoch``
## takes effect.
epoch + 1 + ACTIVATION_EXIT_DELAY
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#get_churn_limit
func get_churn_limit(state: BeaconState): uint64 =
max(
MIN_PER_EPOCH_CHURN_LIMIT,
len(get_active_validator_indices(state, get_current_epoch(state))) div
CHURN_LIMIT_QUOTIENT
).uint64
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#initiate_validator_exit
func initiate_validator_exit*(state: var BeaconState,
index: ValidatorIndex) =
# Initiate the exit of the validator with index ``index``.
# Return if validator already initiated exit
let validator = addr state.validator_registry[index]
if validator.exit_epoch != FAR_FUTURE_EPOCH:
return
# Compute exit queue epoch
# TODO try zero-functional here
let exit_epochs = mapIt(
filterIt(state.validator_registry, it.exit_epoch != FAR_FUTURE_EPOCH),
it.exit_epoch)
var exit_queue_epoch =
max(max(exit_epochs),
get_delayed_activation_exit_epoch(get_current_epoch(state)))
let exit_queue_churn = foldl(
state.validator_registry,
a + (if b.exit_epoch == exit_queue_epoch: 1'u64 else: 0'u64),
0'u64)
if exit_queue_churn >= get_churn_limit(state):
exit_queue_epoch += 1
# Set validator exit epoch and withdrawable epoch
validator.exit_epoch = exit_queue_epoch
validator.withdrawable_epoch =
(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY).Epoch
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#slash_validator
func slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex,
stateCache: var StateCache) =
# Slash the validator with index ``index``.
let current_epoch = get_current_epoch(state)
initiate_validator_exit(state, slashed_index)
state.validator_registry[slashed_index].slashed = true
state.validator_registry[slashed_index].withdrawable_epoch =
current_epoch + LATEST_SLASHED_EXIT_LENGTH
let slashed_balance =
state.validator_registry[slashed_index].effective_balance
state.latest_slashed_balances[current_epoch mod LATEST_SLASHED_EXIT_LENGTH] +=
slashed_balance
let
proposer_index = get_beacon_proposer_index(state, stateCache)
# Spec has whistleblower_index as optional param, but it's never used.
whistleblower_index = proposer_index
whistleblowing_reward = slashed_balance div WHISTLEBLOWING_REWARD_QUOTIENT
proposer_reward = whistleblowing_reward div PROPOSER_REWARD_QUOTIENT
increase_balance(state, proposer_index, proposer_reward)
increase_balance(
state, whistleblower_index, whistleblowing_reward - proposer_reward)
decrease_balance(state, slashed_index, whistleblowing_reward)
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#on-genesis
func get_temporary_block_header(blck: BeaconBlock): BeaconBlockHeader =
## Return the block header corresponding to a block with ``state_root`` set
## to ``ZERO_HASH``.
BeaconBlockHeader(
slot: blck.slot,
parent_root: blck.parent_root,
state_root: ZERO_HASH,
body_root: hash_tree_root(blck.body),
# signing_root(block) is used for block id purposes so signature is a stub
signature: ValidatorSig(),
)
func get_empty_block*(): BeaconBlock =
# Nim default values fill this in mostly correctly.
BeaconBlock(slot: GENESIS_SLOT)
func get_genesis_beacon_state*(
genesis_validator_deposits: openArray[Deposit],
genesis_time: uint64,
genesis_eth1_data: Eth1Data,
flags: UpdateFlags = {}): BeaconState =
## Get the genesis ``BeaconState``.
##
## Before the beacon chain starts, validators will register in the Eth1 chain
## and deposit ETH. When enough many validators have registered, a
## `ChainStart` log will be emitted and the beacon chain can start beaconing.
##
## Because the state root hash is part of the genesis block, the beacon state
## must be calculated before creating the genesis block.
# Induct validators
# Not in spec: the system doesn't work unless there are at least SLOTS_PER_EPOCH
# validators - there needs to be at least one member in each committee -
# good to know for testing, though arguably the system is not that useful at
# at that point :)
doAssert genesis_validator_deposits.len >= SLOTS_PER_EPOCH
var state = BeaconState(
# Misc
slot: GENESIS_SLOT,
genesis_time: genesis_time,
fork: Fork(
previous_version: GENESIS_FORK_VERSION,
current_version: GENESIS_FORK_VERSION,
epoch: GENESIS_EPOCH,
),
# validator_registry and balances automatically initalized
# Randomness and committees
# latest_randao_mixes automatically initialized
# Finality
# previous_epoch_attestations and current_epoch_attestations automatically
# initialized
previous_justified_epoch: GENESIS_EPOCH,
current_justified_epoch: GENESIS_EPOCH,
justification_bitfield: 0,
finalized_epoch: GENESIS_EPOCH,
finalized_root: ZERO_HASH,
# Recent state
# latest_block_roots, latest_state_roots, latest_active_index_roots,
# latest_slashed_balances, and latest_slashed_balances automatically
# initialized
latest_block_header: get_temporary_block_header(get_empty_block()),
# Ethereum 1.0 chain data
# eth1_data_votes automatically initialized
latest_eth1_data: genesis_eth1_data,
deposit_index: 0,
)
# Process genesis deposits
for deposit in genesis_validator_deposits:
discard process_deposit(state, deposit, flags)
# Process genesis activations
for validator_index in 0 ..< state.validator_registry.len:
let validator = addr state.validator_registry[validator_index]
if validator.effective_balance >= MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH
let genesis_active_index_root = hash_tree_root(
get_active_validator_indices(state, GENESIS_EPOCH))
for index in 0 ..< LATEST_ACTIVE_INDEX_ROOTS_LENGTH:
state.latest_active_index_roots[index] = genesis_active_index_root
state
func get_initial_beacon_block*(state: BeaconState): BeaconBlock =
BeaconBlock(
slot: GENESIS_SLOT,
state_root: hash_tree_root(state)
# parent_root, randao_reveal, eth1_data, signature, and body automatically
# initialized to default values.
)
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#get_attestation_data_slot
func get_attestation_data_slot*(state: BeaconState,
data: AttestationData, committee_count: uint64): Slot =
let
offset = (data.crosslink.shard + SHARD_COUNT -
get_start_shard(state, data.target_epoch)) mod SHARD_COUNT
compute_start_slot_of_epoch(data.target_epoch) + offset div
(committee_count div SLOTS_PER_EPOCH)
# This is the slower (O(n)), spec-compatible signature.
func get_attestation_data_slot*(state: BeaconState,
data: AttestationData): Slot =
get_attestation_data_slot(
state, data, get_epoch_committee_count(state, data.target_epoch))
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#get_block_root_at_slot
func get_block_root_at_slot*(state: BeaconState,
slot: Slot): Eth2Digest =
# Return the block root at a recent ``slot``.
doAssert state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT
doAssert slot < state.slot
state.block_roots[slot mod SLOTS_PER_HISTORICAL_ROOT]
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#get_block_root
func get_block_root*(state: BeaconState, epoch: Epoch): Eth2Digest =
# Return the block root at the start of a recent ``epoch``.
get_block_root_at_slot(state, compute_start_slot_of_epoch(epoch))
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#get_total_balance
func get_total_balance*(state: BeaconState, validators: auto): Gwei =
## Return the combined effective balance of the ``indices``. (1 Gwei minimum
## to avoid divisions by zero.)
max(1'u64,
foldl(validators, a + state.validator_registry[b].effective_balance, 0'u64)
)
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#registry-updates
func process_registry_updates*(state: var BeaconState) =
## Process activation eligibility and ejections
## Try to avoid caching here, since this could easily become undefined
for index, validator in state.validator_registry:
if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and
validator.effective_balance >= MAX_EFFECTIVE_BALANCE:
state.validator_registry[index].activation_eligibility_epoch =
get_current_epoch(state)
if is_active_validator(validator, get_current_epoch(state)) and
validator.effective_balance <= EJECTION_BALANCE:
initiate_validator_exit(state, index.ValidatorIndex)
## Queue validators eligible for activation and not dequeued for activation
## prior to finalized epoch
var activation_queue : seq[tuple[a: Epoch, b: int]] = @[]
for index, validator in state.validator_registry:
if validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH and
validator.activation_epoch >=
get_delayed_activation_exit_epoch(state.finalized_epoch):
activation_queue.add (
state.validator_registry[index].activation_eligibility_epoch, index)
activation_queue.sort(system.cmp)
## Dequeued validators for activation up to churn limit (without resetting
## activation epoch)
let churn_limit = get_churn_limit(state)
for i, epoch_and_index in activation_queue:
if i.uint64 >= churn_limit:
break
let
(epoch, index) = epoch_and_index
validator = addr state.validator_registry[index]
if validator.activation_epoch == FAR_FUTURE_EPOCH:
validator.activation_epoch =
get_delayed_activation_exit_epoch(get_current_epoch(state))
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#validate_indexed_attestation
func validate_indexed_attestation*(
state: BeaconState, indexed_attestation: IndexedAttestation): bool =
# Verify validity of ``indexed_attestation`` fields.
let
bit_0_indices = indexed_attestation.custody_bit_0_indices
bit_1_indices = indexed_attestation.custody_bit_1_indices
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
if len(bit_1_indices) != 0:
return false
# Verify max number of indices
let combined_len = len(bit_0_indices) + len(bit_1_indices)
if not (1 <= combined_len and combined_len <= MAX_VALIDATORS_PER_COMMITTEE):
return false
# Verify index sets are disjoint
if len(intersection(toSet(bit_0_indices), toSet(bit_1_indices))) != 0:
return false
# Verify indices are sorted
if bit_0_indices != sorted(bit_0_indices, system.cmp):
return false
if bit_1_indices != sorted(bit_1_indices, system.cmp):
return false
# Verify aggregate signature
bls_verify_multiple(
@[
bls_aggregate_pubkeys(
mapIt(bit_0_indices, state.validator_registry[it.int].pubkey)),
bls_aggregate_pubkeys(
mapIt(bit_1_indices, state.validator_registry[it.int].pubkey)),
],
@[
hash_tree_root(AttestationDataAndCustodyBit(
data: indexed_attestation.data, custody_bit: false)),
hash_tree_root(AttestationDataAndCustodyBit(
data: indexed_attestation.data, custody_bit: true)),
],
indexed_attestation.signature,
get_domain(
state,
DOMAIN_ATTESTATION,
indexed_attestation.data.target_epoch
),
)
# https://github.com/ethereum/eth2.0-specs/blob/v0.7.1/specs/core/0_beacon-chain.md#get_attesting_indices
func get_attesting_indices*(state: BeaconState,
attestation_data: AttestationData,
bitfield: BitField,
stateCache: var StateCache):
HashSet[ValidatorIndex] =
## Return the sorted attesting indices corresponding to ``attestation_data``
## and ``bitfield``.
## The spec goes through a lot of hoops to sort things, and sometimes
## constructs sets from the results here. The basic idea is to always
## just keep it in a HashSet, which seems to suffice. If needed, it's
## possible to follow the spec more literally.
result = initSet[ValidatorIndex]()
let committee =
get_crosslink_committee(
state, attestation_data.target_epoch, attestation_data.crosslink.shard,
stateCache)
doAssert verify_bitfield(bitfield, len(committee))
for i, index in committee:
if get_bitfield_bit(bitfield, i):
result.incl index
func get_attesting_indices_seq*(
state: BeaconState, attestation_data: AttestationData, bitfield: BitField):
seq[ValidatorIndex] =
var cache = get_empty_per_epoch_cache()
toSeq(items(get_attesting_indices(
state, attestation_data, bitfield, cache)))
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.0/specs/core/0_beacon-chain.md#get_indexed_attestation
func get_indexed_attestation(state: BeaconState, attestation: Attestation,
stateCache: var StateCache): IndexedAttestation =
# Return the indexed attestation corresponding to ``attestation``.
let
attesting_indices =
get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, stateCache)
custody_bit_1_indices =
get_attesting_indices(
state, attestation.data, attestation.custody_bits, stateCache)
doAssert custody_bit_1_indices <= attesting_indices
let
## TODO quadratic, .items, but first-class iterators, etc
## filterIt can't work on HashSets directly because it is
## assuming int-indexable thing to extract type, because,
## like lots of other things in sequtils, it's a template
## which doesn't otherwise care about the type system. It
## is a mess. Just write the for-loop, etc, I guess, is a
## reasonable reaction because of the special for binding
## with (non-closure, etc) iterators no other part of Nim
## can access. As such, this function's doing many copies
## and allocations it has no fundamental reason to do.
## TODO phrased in 0.8 as
## custody_bit_0_indices = attesting_indices.difference(custody_bit_1_indices)
custody_bit_0_indices =
filterIt(toSeq(items(attesting_indices)), it notin custody_bit_1_indices)
## TODO No fundamental reason to do so many type conversions
## verify_indexed_attestation checks for sortedness but it's
## entirely a local artifact, seemingly; networking uses the
## Attestation data structure, which can't be unsorted. That
## the conversion here otherwise needs sorting is due to the
## usage of HashSet -- order only matters in one place (that
## 0.6.3 highlights and explicates) except in that the spec,
## for no obvious reason, verifies it.
IndexedAttestation(
custody_bit_0_indices: sorted(
mapIt(custody_bit_0_indices, it.uint64), system.cmp),
# toSeq pointlessly constructs int-indexable copy so mapIt can infer type;
# see above
custody_bit_1_indices:
sorted(mapIt(toSeq(items(custody_bit_1_indices)), it.uint64),
system.cmp),
data: attestation.data,
signature: attestation.signature,
)
# https://github.com/ethereum/eth2.0-specs/blob/v0.6.3/specs/core/0_beacon-chain.md#attestations
proc checkAttestation*(
state: BeaconState, attestation: Attestation, flags: UpdateFlags,
stateCache: var StateCache): bool =
## Process ``Attestation`` operation.
## Check that an attestation follows the rules of being included in the state
## at the current slot. When acting as a proposer, the same rules need to
## be followed!
let stateSlot =
if nextSlot in flags: state.slot + 1
else: state.slot
let
data = attestation.data
attestation_slot = get_attestation_data_slot(state, attestation.data)
if not (attestation_slot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot):
warn("Attestation too new",
attestation_slot = humaneSlotNum(attestation_slot),
state_slot = humaneSlotNum(stateSlot))
return
if not (stateSlot <= attestation_slot + SLOTS_PER_EPOCH):
warn("Attestation too old",
attestation_slot = humaneSlotNum(attestation_slot),
state_slot = humaneSlotNum(stateSlot))
return
let pending_attestation = PendingAttestation(
data: data,
aggregation_bits: attestation.aggregation_bits,
inclusion_delay: state.slot - attestation_slot,
proposer_index: get_beacon_proposer_index(state, stateCache),
)
# Check target epoch, source epoch, source root, and source crosslink
if not (data.target_epoch == get_previous_epoch(state) or
data.target_epoch == get_current_epoch(state)):
warn("Target epoch not current or previous epoch")
return
# Check FFG data, crosslink data, and signature
let ffg_check_data = (data.source_epoch, data.source_root, data.target_epoch)
if data.target_epoch == get_current_epoch(state):
if not (ffg_check_data == (state.current_justified_epoch,
state.current_justified_root, get_current_epoch(state))):
warn("FFG data not matching current justified epoch")
return
#if not (data.crosslink.parent_root ==
# hash_tree_root(state.current_crosslinks[data.crosslink.shard])):
# warn("Crosslink shard's current crosslinks not matching crosslink parent root")
# return
#state.current_epoch_attestations.add(pending_attestation)
else:
if not (ffg_check_data == (state.previous_justified_epoch,
state.previous_justified_root, get_previous_epoch(state))):
warn("FFG data not matching current justified epoch")
return
#if not (data.crosslink.parent_root ==
# hash_tree_root(state.previous_crosslinks[data.crosslink.shard])):
# warn("Crosslink shard's previous crosslinks not matching crosslink parent root")
# return
#state.previous_epoch_attestations.add(pending_attestation)
# TODO un-comment when changed Crosslink to 0.7 structure
#if not (data.crosslink.start_epoch == parent_crosslink.end_epoch):
# warn("Crosslink start and end epochs not the same")
# return
#if not (data.crosslink.end_epoch == min(data.target_epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)):
# warn("Crosslink end epoch incorrect")
# return
# Simlarly, these depend on 0.7 data structures
#assert data.crosslink.parent_root == hash_tree_root(parent_crosslink)
#if not (data.crosslink.data_root == ZERO_HASH): # [to be removed in phase 1]
# warn("Crosslink data root not zero")
# return
# Check signature and bitfields
if not validate_indexed_attestation(
state, get_indexed_attestation(state, attestation, stateCache)):
warn("checkAttestation: signature or bitfields incorrect")
return
true
proc makeAttestationData*(
state: BeaconState, shard: uint64,
beacon_block_root: Eth2Digest): AttestationData =
## Fine points:
## Head must be the head state during the slot that validator is
## part of committee - notably, it can't be a newer or older state (!)
let
epoch_start_slot = compute_start_slot_of_epoch(compute_epoch_of_slot(state.slot))
target_root =
if epoch_start_slot == state.slot: beacon_block_root
else: get_block_root_at_slot(state, epoch_start_slot)
AttestationData(
beacon_block_root: beacon_block_root,
target_root: target_root,
source_epoch: state.current_justified_epoch,
source_root: state.current_justified_root,
target_epoch: compute_epoch_of_slot(state.slot),
crosslink: Crosslink(
# Alternative is to put this offset into all callers
shard: shard + get_start_shard(state, compute_epoch_of_slot(state.slot)),
parent_root: hash_tree_root(state.current_crosslinks[shard]),
data_root: Eth2Digest(), # Stub in phase0
)
)