nimbus-eth2/beacon_chain/spec/beaconstate.nim

713 lines
30 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2020 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
import
tables, algorithm, math, sequtils, options,
json_serialization/std/sets, chronicles,
../extras, ../ssz/merkleization,
./crypto, ./datatypes, ./digest, ./helpers, ./signatures, ./validator,
../../nbench/bench_lab
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#is_valid_merkle_branch
func is_valid_merkle_branch*(leaf: Eth2Digest, branch: openarray[Eth2Digest], depth: uint64, index: uint64, root: Eth2Digest): bool {.nbench.}=
## Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and
## ``branch``.
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] = branch[i.int].data
buf[32..63] = value.data
else:
buf[0..31] = value.data
buf[32..63] = branch[i.int].data
value = eth2digest(buf)
value == root
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/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.12.1/specs/phase0/beacon-chain.md#decrease_balance
func decrease_balance*(
state: var BeaconState, index: ValidatorIndex, delta: Gwei) =
# Decrease the validator balance at index ``index`` 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.12.1/specs/phase0/beacon-chain.md#deposits
proc process_deposit*(preset: RuntimePreset,
state: var BeaconState,
deposit: Deposit,
flags: UpdateFlags = {}): Result[void, cstring] {.nbench.}=
# Process an Eth1 deposit, registering a validator or increasing its balance.
# Verify the Merkle branch
if not is_valid_merkle_branch(
hash_tree_root(deposit.data),
deposit.proof,
DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the `List` length mix-in
state.eth1_deposit_index,
state.eth1_data.deposit_root,
):
return err("process_deposit: deposit Merkle validation failed")
# Deposits must be processed in order
state.eth1_deposit_index += 1
let
pubkey = deposit.data.pubkey
amount = deposit.data.amount
validator_pubkeys = mapIt(state.validators, it.pubkey)
index = validator_pubkeys.find(pubkey)
if index == -1:
# Verify the deposit signature (proof of possession) which is not checked
# by the deposit contract
if skipBLSValidation notin flags:
if not verify_deposit_signature(preset, deposit.data):
# It's ok that deposits fail - they get included in blocks regardless
# TODO spec test?
# TODO: This is temporary set to trace level in order to deal with the
# large number of invalid deposits on Altona
trace "Skipping deposit with invalid signature",
deposit = shortLog(deposit.data)
return ok()
# Add validator and balance entries
state.validators.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)
ok()
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_activation_exit_epoch
func compute_activation_exit_epoch*(epoch: Epoch): Epoch =
## Return the epoch during which validator activations and exits initiated in
## ``epoch`` take effect.
epoch + 1 + MAX_SEED_LOOKAHEAD
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_validator_churn_limit
func get_validator_churn_limit(state: BeaconState, cache: var StateCache): uint64 =
# Return the validator churn limit for the current epoch.
max(MIN_PER_EPOCH_CHURN_LIMIT,
len(cache.shuffled_active_validator_indices).uint64 div CHURN_LIMIT_QUOTIENT)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#initiate_validator_exit
func initiate_validator_exit*(state: var BeaconState,
index: ValidatorIndex, cache: var StateCache) =
# Initiate the exit of the validator with index ``index``.
# Return if validator already initiated exit
let validator = addr state.validators[index]
if validator.exit_epoch != FAR_FUTURE_EPOCH:
return
# Compute exit queue epoch
var exit_epochs = mapIt(
filterIt(state.validators, it.exit_epoch != FAR_FUTURE_EPOCH),
it.exit_epoch)
exit_epochs.add compute_activation_exit_epoch(get_current_epoch(state))
var exit_queue_epoch = max(exit_epochs)
let exit_queue_churn = foldl(
state.validators,
a + (if b.exit_epoch == exit_queue_epoch: 1'u64 else: 0'u64),
0'u64)
if exit_queue_churn >= get_validator_churn_limit(state, cache):
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
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#slash_validator
proc slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex,
cache: var StateCache) =
# Slash the validator with index ``index``.
let epoch = get_current_epoch(state)
initiate_validator_exit(state, slashed_index, cache)
let validator = addr state.validators[slashed_index]
debug "slash_validator: ejecting validator via slashing (validator_leaving)",
index = slashed_index,
num_validators = state.validators.len,
current_epoch = get_current_epoch(state),
validator_slashed = validator.slashed,
validator_withdrawable_epoch = validator.withdrawable_epoch,
validator_exit_epoch = validator.exit_epoch,
validator_effective_balance = validator.effective_balance
validator.slashed = true
validator.withdrawable_epoch =
max(validator.withdrawable_epoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR)
state.slashings[int(epoch mod EPOCHS_PER_SLASHINGS_VECTOR)] +=
validator.effective_balance
decrease_balance(state, slashed_index,
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT)
# The rest doesn't make sense without there being any proposer index, so skip
let proposer_index = get_beacon_proposer_index(state, cache)
if proposer_index.isNone:
debug "No beacon proposer index and probably no active validators"
return
# Apply proposer and whistleblower rewards
let
# Spec has whistleblower_index as optional param, but it's never used.
whistleblower_index = proposer_index.get
whistleblowing_reward =
(validator.effective_balance div WHISTLEBLOWER_REWARD_QUOTIENT).Gwei
proposer_reward = whistleblowing_reward div PROPOSER_REWARD_QUOTIENT
increase_balance(state, proposer_index.get, proposer_reward)
# TODO: evaluate if spec bug / underflow can be triggered
doAssert(whistleblowing_reward >= proposer_reward, "Spec bug: underflow in slash_validator")
increase_balance(
state, whistleblower_index, whistleblowing_reward - proposer_reward)
func genesis_time_from_eth1_timestamp*(preset: RuntimePreset, eth1_timestamp: uint64): uint64 =
# TODO: remove once we switch completely to v0.12.1
when SPEC_VERSION == "0.12.1":
eth1_timestamp + preset.GENESIS_DELAY
else:
const SECONDS_PER_DAY = uint64(60*60*24)
eth1_timestamp + 2'u64 * SECONDS_PER_DAY - (eth1_timestamp mod SECONDS_PER_DAY)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#genesis
proc initialize_beacon_state_from_eth1*(
preset: RuntimePreset,
eth1_block_hash: Eth2Digest,
eth1_timestamp: uint64,
deposits: openArray[Deposit],
flags: UpdateFlags = {}): BeaconStateRef {.nbench.} =
## 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 deposits.len >= SLOTS_PER_EPOCH.int
var state = BeaconStateRef(
fork: Fork(
previous_version: preset.GENESIS_FORK_VERSION,
current_version: preset.GENESIS_FORK_VERSION,
epoch: GENESIS_EPOCH),
genesis_time: genesis_time_from_eth1_timestamp(preset, eth1_timestamp),
eth1_data:
Eth1Data(block_hash: eth1_block_hash, deposit_count: uint64(len(deposits))),
latest_block_header:
BeaconBlockHeader(
body_root: hash_tree_root(BeaconBlockBody(
# This differs from the spec intentionally.
# We must specify the default value for `ValidatorSig`
# in order to get a correct `hash_tree_root`.
randao_reveal: ValidatorSig(kind: OpaqueBlob)
))
)
)
# Seed RANDAO with Eth1 entropy
state.randao_mixes.fill(eth1_block_hash)
# Process deposits
let
leaves = deposits.mapIt(it.data)
var i = 0
for prefix_root in hash_tree_roots_prefix(
leaves, 2'i64^DEPOSIT_CONTRACT_TREE_DEPTH):
state.eth1_data.deposit_root = prefix_root
discard process_deposit(preset, state[], deposits[i], flags)
i += 1
# Process activations
for validator_index in 0 ..< state.validators.len:
let
balance = state.balances[validator_index]
validator = addr state.validators[validator_index]
validator.effective_balance = min(
balance - balance mod EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
if validator.effective_balance == MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH
# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = hash_tree_root(state.validators)
state
proc initialize_hashed_beacon_state_from_eth1*(
preset: RuntimePreset,
eth1_block_hash: Eth2Digest,
eth1_timestamp: uint64,
deposits: openArray[Deposit],
flags: UpdateFlags = {}): HashedBeaconState =
let genesisState = initialize_beacon_state_from_eth1(
preset, eth1_block_hash, eth1_timestamp, deposits, flags)
HashedBeaconState(data: genesisState[], root: hash_tree_root(genesisState[]))
func is_valid_genesis_state*(preset: RuntimePreset,
state: BeaconState,
active_validator_indices: seq[ValidatorIndex]): bool =
if state.genesis_time < preset.MIN_GENESIS_TIME:
return false
# This is an okay get_active_validator_indices(...) for the time being.
if active_validator_indices.len.uint64 < preset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
return false
return true
# TODO this is now a non-spec helper function, and it's not really accurate
# so only usable/used in research/ and tests/
func get_initial_beacon_block*(state: BeaconState): SignedBeaconBlock =
let message = BeaconBlock(
slot: GENESIS_SLOT,
state_root: hash_tree_root(state),
body: BeaconBlockBody(
# TODO: This shouldn't be necessary if OpaqueBlob is the default
randao_reveal: ValidatorSig(kind: OpaqueBlob)))
# parent_root, randao_reveal, eth1_data, signature, and body automatically
# initialized to default values.
SignedBeaconBlock(message: message, root: hash_tree_root(message))
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/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.12.1/specs/phase0/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_at_epoch(epoch))
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_total_balance
func get_total_balance*(state: BeaconState, validators: auto): Gwei =
## Return the combined effective balance of the ``indices``.
## ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero.
## Math safe up to ~10B ETH, afterwhich this overflows uint64.
max(EFFECTIVE_BALANCE_INCREMENT,
foldl(validators, a + state.validators[b].effective_balance, 0'u64)
)
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#is_eligible_for_activation_queue
func is_eligible_for_activation_queue(validator: Validator): bool =
# Check if ``validator`` is eligible to be placed into the activation queue.
validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and
validator.effective_balance == MAX_EFFECTIVE_BALANCE
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#is_eligible_for_activation
func is_eligible_for_activation(state: BeaconState, validator: Validator):
bool =
# Check if ``validator`` is eligible for activation.
# Placement in queue is finalized
validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch and
# Has not yet been activated
validator.activation_epoch == FAR_FUTURE_EPOCH
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#registry-updates
proc process_registry_updates*(state: var BeaconState,
cache: var StateCache) {.nbench.}=
## Process activation eligibility and ejections
## Try to avoid caching here, since this could easily become undefined
# Make visible, e.g.,
# https://github.com/status-im/nim-beacon-chain/pull/608
# https://github.com/sigp/lighthouse/pull/657
let epoch {.used.} = get_current_epoch(state)
trace "process_registry_updates validator balances",
balances=state.balances,
active_validator_indices=get_active_validator_indices(state, epoch),
epoch=epoch
# is_active_validator(...) is activation_epoch <= epoch < exit_epoch,
# and changes here to either activation_epoch or exit_epoch only take
# effect with a compute_activation_exit_epoch(...) delay of, based on
# the current epoch, 1 + MAX_SEED_LOOKAHEAD epochs ahead. Thus caches
# remain valid for this epoch through though this function along with
# the rest of the epoch transition.
for index, validator in state.validators:
if is_eligible_for_activation_queue(validator):
state.validators[index].activation_eligibility_epoch =
get_current_epoch(state) + 1
if is_active_validator(validator, get_current_epoch(state)) and
validator.effective_balance <= EJECTION_BALANCE:
debug "Registry updating: ejecting validator due to low balance (validator_leaving)",
index = index,
num_validators = state.validators.len,
current_epoch = get_current_epoch(state),
validator_slashed = validator.slashed,
validator_withdrawable_epoch = validator.withdrawable_epoch,
validator_exit_epoch = validator.exit_epoch,
validator_effective_balance = validator.effective_balance
initiate_validator_exit(state, index.ValidatorIndex, cache)
## Queue validators eligible for activation and not dequeued for activation
var activation_queue : seq[tuple[a: Epoch, b: int]] = @[]
for index, validator in state.validators:
if is_eligible_for_activation(state, validator):
activation_queue.add (
state.validators[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_validator_churn_limit(state, cache)
for i, epoch_and_index in activation_queue:
if i.uint64 >= churn_limit:
break
let
(_, index) = epoch_and_index
validator = addr state.validators[index]
validator.activation_epoch =
compute_activation_exit_epoch(get_current_epoch(state))
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#is_valid_indexed_attestation
func is_valid_indexed_attestation*(
state: BeaconState, indexed_attestation: SomeIndexedAttestation,
flags: UpdateFlags): bool =
# Check if ``indexed_attestation`` is not empty, has sorted and unique
# indices and has a valid aggregate signature.
template is_sorted_and_unique(s: untyped): bool =
for i in 1 ..< s.len:
if s[i - 1].uint64 >= s[i].uint64:
return false
true
# Not from spec, but this function gets used in front-line roles, not just
# behind firewall.
let num_validators = state.validators.len.uint64
if anyIt(indexed_attestation.attesting_indices, it >= num_validators):
trace "indexed attestation: not all indices valid validators"
return false
# Verify indices are sorted and unique
let indices = indexed_attestation.attesting_indices.asSeq
if len(indices) == 0 or not is_sorted_and_unique(indices):
trace "indexed attestation: indices not sorted and unique"
return false
# Verify aggregate signature
if skipBLSValidation notin flags:
# TODO: fuse loops with blsFastAggregateVerify
let pubkeys = mapIt(indices, state.validators[it.int].pubkey)
if not verify_attestation_signature(
state.fork, state.genesis_validators_root, indexed_attestation.data,
pubkeys, indexed_attestation.signature):
trace "indexed attestation: signature verification failure"
return false
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_attesting_indices
func get_attesting_indices*(state: BeaconState,
data: AttestationData,
bits: CommitteeValidatorsBits,
stateCache: var StateCache):
HashSet[ValidatorIndex] =
# Return the set of attesting indices corresponding to ``data`` and ``bits``.
result = initHashSet[ValidatorIndex]()
let committee = get_beacon_committee(
state, data.slot, data.index.CommitteeIndex, stateCache)
# This shouldn't happen if one begins with a valid BeaconState and applies
# valid updates, but one can construct a BeaconState where it does. Do not
# do anything here since the PendingAttestation wouldn't have made it past
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#attestations
# which checks len(attestation.aggregation_bits) == len(committee) that in
# nim-beacon-chain lives in check_attestation(...).
# Addresses https://github.com/status-im/nim-beacon-chain/issues/922
if bits.len != committee.len:
trace "get_attesting_indices: inconsistent aggregation and committee length"
return
for i, index in committee:
if bits[i]:
result.incl index
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/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)
IndexedAttestation(
attesting_indices:
List[uint64, Limit MAX_VALIDATORS_PER_COMMITTEE].init(
sorted(mapIt(attesting_indices.toSeq, it.uint64), system.cmp)),
data: attestation.data,
signature: attestation.signature
)
func get_indexed_attestation*(state: BeaconState, attestation: TrustedAttestation,
stateCache: var StateCache): TrustedIndexedAttestation =
# Return the indexed attestation corresponding to ``attestation``.
let
attesting_indices =
get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, stateCache)
TrustedIndexedAttestation(
attesting_indices:
List[uint64, Limit MAX_VALIDATORS_PER_COMMITTEE].init(
sorted(mapIt(attesting_indices.toSeq, it.uint64), system.cmp)),
data: attestation.data,
signature: attestation.signature
)
# Attestation validation
# ------------------------------------------------------------------------------------------
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.3/specs/phase0/beacon-chain.md#attestations
#
# TODO: Refactor, we need:
# - cheap validations that do not involve the fork state (to avoid unnecessary and costly rewinding which are probably a DOS vector)
# - check that must be done on the targeted state (for example beacon committee consistency)
# - check that requires easily computed data from the targeted sate (slots, epoch, justified checkpoints)
proc isValidAttestationSlot*(attestationSlot, stateSlot: Slot): bool =
## Attestation check that does not depend on the whole block state
## for cheaper prefiltering of attestations that come from the network
## to limit DOS via state rewinding
if not (attestationSlot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot):
warn("Attestation too new",
attestation_slot = shortLog(attestationSlot),
state_slot = shortLog(stateSlot))
return false
if not (stateSlot <= attestationSlot + SLOTS_PER_EPOCH):
warn("Attestation too old",
attestation_slot = shortLog(attestationSlot),
state_slot = shortLog(stateSlot))
return false
return true
# TODO remove/merge with p2p-interface validation
proc isValidAttestationTargetEpoch*(
state: BeaconState, data: AttestationData): bool =
# TODO what constitutes a valid attestation when it's about to be added to
# the pool? we're interested in attestations that will become viable
# for inclusion in blocks in the future and on any fork, so we need to
# consider that validations might happen using the state of a different
# fork.
# Some things are always invalid (like out-of-bounds issues etc), but
# others are more subtle - how do we validate the signature for example?
# It might be valid on one fork but not another. One thing that helps
# is that committees are stable per epoch and that it should be OK to
# include an attestation in a block even if the corresponding validator
# was slashed in the same epoch - there's no penalty for doing this and
# the vote counting logic will take care of any ill effects (TODO verify)
# TODO re-enable check
#if not (data.crosslink.shard < SHARD_COUNT):
# notice "Attestation shard too high",
# attestation_shard = data.crosslink.shard
# return
# Without this check, we can't get a slot number for the attestation as
# certain helpers will assert
# TODO this could probably be avoided by being smart about the specific state
# used to validate the attestation: most likely if we pick the state of
# the beacon block being voted for and a slot in the target epoch
# of the attestation, we'll be safe!
# TODO the above state selection logic should probably live here in the
# attestation pool
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
if not (data.target.epoch == compute_epoch_at_slot(data.slot)):
warn("Target epoch inconsistent with epoch of data slot",
target_epoch = data.target.epoch,
data_slot_epoch = compute_epoch_at_slot(data.slot))
return
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#attestations
proc check_attestation*(
state: BeaconState, attestation: SomeAttestation, flags: UpdateFlags,
stateCache: var StateCache): bool =
## 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!
# TODO: When checking attestation from the network, currently rewinding/advancing
# a temporary state is needed.
# If this is too costly we can be DOS-ed.
# We might want to split this into "state-dependent" checks and "state-independent" checks
# The latter one should be made prior to state-rewinding.
let
stateSlot = state.slot
data = attestation.data
logScope:
attestation = shortLog(attestation)
trace "process_attestation: beginning"
let committee_count_at_slot =
get_committee_count_at_slot(get_shuffled_active_validator_indices(
state, stateSlot.compute_epoch_at_slot, stateCache).len.uint64).uint64
if not (data.index < committee_count_at_slot):
warn "Data index exceeds committee count",
committee_count = committee_count_at_slot
return
if not isValidAttestationTargetEpoch(state, data):
# Logging in isValidAttestationTargetEpoch
return
if not isValidAttestationSlot(data.slot, stateSlot):
# Logging in isValidAttestationSlot
return
let committee = get_beacon_committee(
state, data.slot, data.index.CommitteeIndex, stateCache)
if attestation.aggregation_bits.len != committee.len:
warn "Inconsistent aggregation and committee length",
aggregation_bits_len = attestation.aggregation_bits.len,
committee_len = committee.len
return
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_checkpoint.epoch,
state.current_justified_checkpoint.root, get_current_epoch(state))):
warn "FFG data not matching current justified epoch"
return
else:
if not (ffg_check_data == (state.previous_justified_checkpoint.epoch,
state.previous_justified_checkpoint.root, get_previous_epoch(state))):
warn "FFG data not matching previous justified epoch"
return
if not is_valid_indexed_attestation(
state, get_indexed_attestation(state, attestation, stateCache), flags):
warn "process_attestation: signature or bitfields incorrect"
return
true
proc process_attestation*(
state: var BeaconState, attestation: SomeAttestation, flags: UpdateFlags,
stateCache: var StateCache): Result[void, cstring] {.nbench.} =
# In the spec, attestation validation is mixed with state mutation, so here
# we've split it into two functions so that the validation logic can be
# reused when looking for suitable blocks to include in attestations.
# TODO don't log warnings when looking for attestations (return
# Result[void, cstring] instead of logging in check_attestation?)
let proposer_index = get_beacon_proposer_index(state, stateCache)
if proposer_index.isNone:
return err("process_attestation: no beacon proposer index and probably no active validators")
if check_attestation(state, attestation, flags, stateCache):
let
attestation_slot = attestation.data.slot
pending_attestation = PendingAttestation(
data: attestation.data,
aggregation_bits: attestation.aggregation_bits,
inclusion_delay: state.slot - attestation_slot,
proposer_index: proposer_index.get.uint64,
)
if attestation.data.target.epoch == get_current_epoch(state):
trace "process_attestation: current_epoch_attestations.add",
attestation = shortLog(attestation),
pending_attestation = pending_attestation,
indices = get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, stateCache).len
state.current_epoch_attestations.add(pending_attestation)
else:
trace "process_attestation: previous_epoch_attestations.add",
attestation = shortLog(attestation),
pending_attestation = pending_attestation,
indices = get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, stateCache).len
state.previous_epoch_attestations.add(pending_attestation)
ok()
else:
err("process_attestation: check_attestation failed")
func makeAttestationData*(
state: BeaconState, slot: Slot, committee_index: uint64,
beacon_block_root: Eth2Digest): AttestationData =
## Create an attestation / vote for the block `beacon_block_root` using the
## data in `state` to fill in the rest of the fields.
## `state` is the state corresponding to the `beacon_block_root` advanced to
## the slot we're attesting to.
let
current_epoch = get_current_epoch(state)
start_slot = compute_start_slot_at_epoch(current_epoch)
epoch_boundary_block_root =
if start_slot == state.slot: beacon_block_root
else: get_block_root_at_slot(state, start_slot)
doAssert slot.compute_epoch_at_slot == current_epoch,
"Computed epoch was " & $slot.compute_epoch_at_slot &
" while the state current_epoch was " & $current_epoch
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#attestation-data
AttestationData(
slot: slot,
index: committee_index,
beacon_block_root: beacon_block_root,
source: state.current_justified_checkpoint,
target: Checkpoint(
epoch: current_epoch,
root: epoch_boundary_block_root
)
)