318 lines
14 KiB
Nim
318 lines
14 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2018-2024 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: [].}
|
|
|
|
import
|
|
std/sequtils,
|
|
results,
|
|
chronicles,
|
|
../extras,
|
|
../spec/[beaconstate, helpers, network, signatures, validator],
|
|
../spec/datatypes/base,
|
|
./block_pools_types, blockchain_dag
|
|
|
|
from ../spec/datatypes/electra import shortLog
|
|
|
|
export
|
|
base, extras, block_pools_types, results
|
|
|
|
logScope: topics = "spec_cache"
|
|
|
|
# Spec functions implemented based on cached values instead of the full state
|
|
func count_active_validators*(shufflingRef: ShufflingRef): uint64 =
|
|
shufflingRef.shuffled_active_validator_indices.lenu64
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#get_committee_count_per_slot
|
|
func get_committee_count_per_slot*(shufflingRef: ShufflingRef): uint64 =
|
|
get_committee_count_per_slot(count_active_validators(shufflingRef))
|
|
|
|
iterator get_committee_indices*(shufflingRef: ShufflingRef): CommitteeIndex =
|
|
let committees_per_slot = get_committee_count_per_slot(shufflingRef)
|
|
for committee_index in get_committee_indices(committees_per_slot):
|
|
yield committee_index
|
|
|
|
func get_committee_index*(shufflingRef: ShufflingRef, index: uint64):
|
|
Result[CommitteeIndex, cstring] =
|
|
check_attestation_index(index, get_committee_count_per_slot(shufflingRef))
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#get_beacon_committee
|
|
iterator get_beacon_committee*(
|
|
shufflingRef: ShufflingRef, slot: Slot, committee_index: CommitteeIndex):
|
|
(int, ValidatorIndex) =
|
|
## Return the beacon committee at ``slot`` for ``index``.
|
|
doAssert slot.epoch == shufflingRef.epoch
|
|
let committees_per_slot = get_committee_count_per_slot(shufflingRef)
|
|
for index_in_committee, idx in compute_committee(
|
|
shufflingRef.shuffled_active_validator_indices,
|
|
(slot mod SLOTS_PER_EPOCH) * committees_per_slot + committee_index.asUInt64,
|
|
committees_per_slot * SLOTS_PER_EPOCH
|
|
): yield (index_in_committee, idx)
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/beacon-chain.md#get_beacon_committee
|
|
func get_beacon_committee*(
|
|
shufflingRef: ShufflingRef, slot: Slot, committee_index: CommitteeIndex):
|
|
seq[ValidatorIndex] =
|
|
## Return the beacon committee at ``slot`` for ``index``.
|
|
doAssert slot.epoch == shufflingRef.epoch
|
|
let committees_per_slot = get_committee_count_per_slot(shufflingRef)
|
|
compute_committee(
|
|
shufflingRef.shuffled_active_validator_indices,
|
|
(slot mod SLOTS_PER_EPOCH) * committees_per_slot + committee_index.asUInt64,
|
|
committees_per_slot * SLOTS_PER_EPOCH
|
|
)
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#get_beacon_committee
|
|
func get_beacon_committee_len*(
|
|
shufflingRef: ShufflingRef, slot: Slot, committee_index: CommitteeIndex): uint64 =
|
|
## Return the number of members in the beacon committee at ``slot`` for ``index``.
|
|
doAssert slot.epoch == shufflingRef.epoch
|
|
let committees_per_slot = get_committee_count_per_slot(shufflingRef)
|
|
compute_committee_len(
|
|
count_active_validators(shufflingRef),
|
|
(slot mod SLOTS_PER_EPOCH) * committees_per_slot + committee_index.asUInt64,
|
|
committees_per_slot * SLOTS_PER_EPOCH
|
|
)
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#get_attesting_indices
|
|
func compatible_with_shuffling*(
|
|
bits: CommitteeValidatorsBits | ElectraCommitteeValidatorsBits,
|
|
shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_index: CommitteeIndex): bool =
|
|
bits.lenu64 == get_beacon_committee_len(shufflingRef, slot, committee_index)
|
|
|
|
iterator get_attesting_indices*(shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_index: CommitteeIndex,
|
|
bits: CommitteeValidatorsBits | ElectraCommitteeValidatorsBits):
|
|
ValidatorIndex =
|
|
if not bits.compatible_with_shuffling(shufflingRef, slot, committee_index):
|
|
trace "get_attesting_indices: inconsistent aggregation and committee length"
|
|
else:
|
|
for index_in_committee, validator_index in get_beacon_committee(
|
|
shufflingRef, slot, committee_index):
|
|
if bits[index_in_committee]:
|
|
yield validator_index
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.0/specs/electra/beacon-chain.md#modified-get_attesting_indices
|
|
iterator get_attesting_indices*(shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_bits: AttestationCommitteeBits,
|
|
aggregation_bits: ElectraCommitteeValidatorsBits, on_chain: static bool):
|
|
ValidatorIndex =
|
|
when on_chain:
|
|
var pos = 0
|
|
for committee_index in get_committee_indices(committee_bits):
|
|
for _, validator_index in get_beacon_committee(
|
|
shufflingRef, slot, committee_index):
|
|
|
|
if aggregation_bits[pos]:
|
|
yield validator_index
|
|
pos += 1
|
|
else:
|
|
let committee_index = get_committee_index_one(committee_bits)
|
|
for validator_index in get_attesting_indices(
|
|
shufflingRef, slot, committee_index, aggregation_bits, on_chain):
|
|
yield validator_index
|
|
|
|
iterator get_attesting_indices*(
|
|
dag: ChainDAGRef, attestation: phase0.TrustedAttestation,
|
|
on_chain: static bool = true): ValidatorIndex =
|
|
block gaiBlock: # `return` is not allowed in an inline iterator
|
|
let
|
|
slot =
|
|
check_attestation_slot_target(attestation.data).valueOr:
|
|
warn "Invalid attestation slot in trusted attestation",
|
|
attestation = shortLog(attestation)
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
blck =
|
|
dag.getBlockRef(attestation.data.beacon_block_root).valueOr:
|
|
# Attestation block unknown - this is fairly common because we
|
|
# discard alternative histories on restart
|
|
debug "Pruned block in trusted attestation",
|
|
attestation = shortLog(attestation)
|
|
break gaiBlock
|
|
target =
|
|
blck.atCheckpoint(attestation.data.target).valueOr:
|
|
# This may happen when there's no block at the epoch boundary slot
|
|
# leading to the case where the attestation block root is the
|
|
# finalized head (exists as BlockRef) but its target vote has
|
|
# already been pruned
|
|
notice "Pruned target in trusted attestation",
|
|
blck = shortLog(blck),
|
|
attestation = shortLog(attestation)
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
shufflingRef =
|
|
dag.getShufflingRef(target.blck, target.slot.epoch, false).valueOr:
|
|
warn "Attestation shuffling not found",
|
|
blck = shortLog(blck),
|
|
attestation = shortLog(attestation)
|
|
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
|
|
committeesPerSlot = get_committee_count_per_slot(shufflingRef)
|
|
committeeIndex =
|
|
CommitteeIndex.init(attestation.data.index, committeesPerSlot).valueOr:
|
|
warn "Unexpected committee index in trusted attestation",
|
|
blck = shortLog(blck),
|
|
attestation = shortLog(attestation)
|
|
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
|
|
for validator in get_attesting_indices(
|
|
shufflingRef, slot, committeeIndex, attestation.aggregation_bits):
|
|
yield validator
|
|
|
|
iterator get_attesting_indices*(
|
|
dag: ChainDAGRef, attestation: electra.TrustedAttestation,
|
|
on_chain: static bool): ValidatorIndex =
|
|
block gaiBlock: # `return` is not allowed in an inline iterator
|
|
let
|
|
slot =
|
|
check_attestation_slot_target(attestation.data).valueOr:
|
|
warn "Invalid attestation slot in trusted attestation",
|
|
attestation = shortLog(attestation)
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
blck =
|
|
dag.getBlockRef(attestation.data.beacon_block_root).valueOr:
|
|
# Attestation block unknown - this is fairly common because we
|
|
# discard alternative histories on restart
|
|
debug "Pruned block in trusted attestation",
|
|
attestation = shortLog(attestation)
|
|
break gaiBlock
|
|
target =
|
|
blck.atCheckpoint(attestation.data.target).valueOr:
|
|
# This may happen when there's no block at the epoch boundary slot
|
|
# leading to the case where the attestation block root is the
|
|
# finalized head (exists as BlockRef) but its target vote has
|
|
# already been pruned
|
|
notice "Pruned target in trusted attestation",
|
|
blck = shortLog(blck),
|
|
attestation = shortLog(attestation)
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
shufflingRef =
|
|
dag.getShufflingRef(target.blck, target.slot.epoch, false).valueOr:
|
|
warn "Attestation shuffling not found",
|
|
blck = shortLog(blck),
|
|
attestation = shortLog(attestation)
|
|
|
|
doAssert strictVerification notin dag.updateFlags
|
|
break gaiBlock
|
|
|
|
for validator in get_attesting_indices(
|
|
shufflingRef, slot, attestation.committee_bits,
|
|
attestation.aggregation_bits, on_chain):
|
|
yield validator
|
|
|
|
func get_attesting_indices_one*(shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_index: CommitteeIndex,
|
|
bits: CommitteeValidatorsBits | ElectraCommitteeValidatorsBits):
|
|
Option[ValidatorIndex] =
|
|
# A variation on get_attesting_indices that returns the validator index only
|
|
# if only one validator index is set
|
|
var res = none(ValidatorIndex)
|
|
for validator_index in get_attesting_indices(
|
|
shufflingRef, slot, committee_index, bits):
|
|
if res.isSome(): return none(ValidatorIndex)
|
|
res = some(validator_index)
|
|
res
|
|
|
|
func get_attesting_indices_one*(shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_bits: AttestationCommitteeBits,
|
|
aggregation_bits: ElectraCommitteeValidatorsBits,
|
|
on_chain: static bool):
|
|
Opt[ValidatorIndex] =
|
|
# A variation on get_attesting_indices that returns the validator index only
|
|
# if only one validator index is set
|
|
static: doAssert not on_chain, "only on_chain supported"
|
|
|
|
var res = Opt.none(ValidatorIndex)
|
|
let committee_index = ? get_committee_index_one(committee_bits)
|
|
for validator_index in get_attesting_indices(
|
|
shufflingRef, slot, committee_index, aggregation_bits):
|
|
if res.isSome(): return Opt.none(ValidatorIndex)
|
|
res = Opt.some(validator_index)
|
|
res
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#get_attesting_indices
|
|
func get_attesting_indices*(shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_index: CommitteeIndex,
|
|
bits: CommitteeValidatorsBits):
|
|
seq[ValidatorIndex] =
|
|
for idx in get_attesting_indices(shufflingRef, slot, committee_index, bits):
|
|
result.add(idx)
|
|
|
|
func get_attesting_indices*(shufflingRef: ShufflingRef,
|
|
slot: Slot,
|
|
committee_index: CommitteeIndex,
|
|
bits: ElectraCommitteeValidatorsBits,
|
|
on_chain: static bool):
|
|
seq[ValidatorIndex] =
|
|
static: doAssert not on_chain, "only on_chain supported"
|
|
|
|
for idx in get_attesting_indices(shufflingRef, slot, committee_index, bits):
|
|
result.add(idx)
|
|
|
|
func makeAttestationData*(
|
|
epochRef: EpochRef, bs: BlockSlot,
|
|
committee_index: CommitteeIndex): AttestationData =
|
|
## Create an attestation / vote for the block `bs` using the
|
|
## data in `epochRef` to fill in the rest of the fields.
|
|
## `epochRef` is the epoch information corresponding to the `bs` advanced to
|
|
## the slot we're attesting to.
|
|
|
|
let
|
|
slot = bs.slot
|
|
current_epoch = slot.epoch()
|
|
epoch_boundary_slot = current_epoch.start_slot()
|
|
epoch_boundary_block = bs.blck.atSlot(epoch_boundary_slot)
|
|
|
|
doAssert current_epoch == epochRef.epoch
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/validator.md#attestation-data
|
|
AttestationData(
|
|
slot: slot,
|
|
index: committee_index.asUInt64,
|
|
beacon_block_root: bs.blck.root,
|
|
source: epochRef.checkpoints.justified,
|
|
target: Checkpoint(
|
|
epoch: current_epoch,
|
|
root: epoch_boundary_block.blck.root))
|
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#validator-assignments
|
|
iterator get_committee_assignments*(
|
|
shufflingRef: ShufflingRef, validator_indices: HashSet[ValidatorIndex]):
|
|
tuple[committee_index: CommitteeIndex,
|
|
subnet_id: SubnetId, slot: Slot] =
|
|
let
|
|
committees_per_slot = get_committee_count_per_slot(shufflingRef)
|
|
epoch = shufflingRef.epoch
|
|
|
|
for slot in epoch.slots():
|
|
for committee_index in get_committee_indices(committees_per_slot):
|
|
if anyIt(get_beacon_committee(shufflingRef, slot, committee_index), it in validator_indices):
|
|
yield (
|
|
committee_index,
|
|
compute_subnet_for_attestation(committees_per_slot, slot, committee_index),
|
|
slot)
|
|
|
|
func is_aggregator*(shufflingRef: ShufflingRef, slot: Slot,
|
|
index: CommitteeIndex, slot_signature: ValidatorSig): bool =
|
|
let
|
|
committee_len = get_beacon_committee_len(shufflingRef, slot, index)
|
|
return is_aggregator(committee_len, slot_signature)
|