spec cleanups (#1379)
* add helper for beacon committee length (used for quickly validating attestations) * refactor some attestation checks to do cheap checks early * validate attestation epoch before computing active validator set * clean up documentation / comments * fill state cache on demand
This commit is contained in:
parent
99dcb81e77
commit
48cebc7157
|
@ -22,13 +22,14 @@ logScope:
|
|||
func is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex,
|
||||
slot_signature: ValidatorSig, cache: var StateCache): bool =
|
||||
let
|
||||
committee = get_beacon_committee(state, slot, index, cache)
|
||||
modulo = max(1'u64, len(committee).uint64 div TARGET_AGGREGATORS_PER_COMMITTEE)
|
||||
committee_len = get_beacon_committee_len(state, slot, index, cache)
|
||||
modulo = max(1'u64, committee_len div TARGET_AGGREGATORS_PER_COMMITTEE)
|
||||
bytes_to_uint64(eth2digest(slot_signature.toRaw()).data[0..7]) mod modulo == 0
|
||||
|
||||
proc aggregate_attestations*(
|
||||
pool: AttestationPool, state: BeaconState, index: CommitteeIndex,
|
||||
privkey: ValidatorPrivKey, trailing_distance: uint64): Option[AggregateAndProof] =
|
||||
privkey: ValidatorPrivKey, trailing_distance: uint64,
|
||||
cache: var StateCache): Option[AggregateAndProof] =
|
||||
doAssert state.slot >= trailing_distance
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#configuration
|
||||
|
@ -42,8 +43,6 @@ proc aggregate_attestations*(
|
|||
doAssert slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= state.slot
|
||||
doAssert state.slot >= slot
|
||||
|
||||
var cache = StateCache()
|
||||
# TODO performance issue for future, via get_active_validator_indices(...)
|
||||
doAssert index.uint64 < get_committee_count_per_slot(state, slot.epoch, cache)
|
||||
|
||||
# TODO for testing purposes, refactor this into the condition check
|
||||
|
@ -99,7 +98,12 @@ proc isValidAttestationSlot(
|
|||
|
||||
true
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/p2p-interface.md#attestation-subnets
|
||||
func checkPropagationSlotRange(data: AttestationData, current_slot: Slot): bool =
|
||||
# TODO clock disparity
|
||||
(data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot) and
|
||||
(current_slot >= data.slot)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#attestation-subnets
|
||||
proc isValidAttestation*(
|
||||
pool: var AttestationPool, attestation: Attestation, current_slot: Slot,
|
||||
topicCommitteeIndex: uint64): bool =
|
||||
|
@ -107,8 +111,12 @@ proc isValidAttestation*(
|
|||
topics = "att_aggr valid_att"
|
||||
received_attestation = shortLog(attestation)
|
||||
|
||||
if not (attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >=
|
||||
current_slot and current_slot >= attestation.data.slot):
|
||||
# TODO https://github.com/ethereum/eth2.0-specs/issues/1998
|
||||
if (let v = check_attestation_slot_target(attestation.data); v.isErr):
|
||||
debug "Invalid attestation", err = v.error
|
||||
return false
|
||||
|
||||
if not checkPropagationSlotRange(attestation.data, current_slot):
|
||||
debug "attestation.data.slot not within ATTESTATION_PROPAGATION_SLOT_RANGE"
|
||||
return false
|
||||
|
||||
|
@ -202,6 +210,11 @@ proc isValidAggregatedAttestation*(
|
|||
logScope:
|
||||
aggregate = shortLog(aggregate)
|
||||
|
||||
# TODO https://github.com/ethereum/eth2.0-specs/issues/1998
|
||||
if (let v = check_attestation_slot_target(aggregate.data); v.isErr):
|
||||
debug "Invalid aggregate", err = v.error
|
||||
return false
|
||||
|
||||
# There's some overlap between this and isValidAttestation(), but unclear if
|
||||
# saving a few lines of code would balance well with losing straightforward,
|
||||
# spec-based synchronization.
|
||||
|
@ -210,8 +223,7 @@ proc isValidAggregatedAttestation*(
|
|||
# ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a
|
||||
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. aggregate.data.slot +
|
||||
# ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
||||
if not (aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >=
|
||||
current_slot and current_slot >= aggregate.data.slot):
|
||||
if not checkPropagationSlotRange(aggregate.data, current_slot):
|
||||
debug "aggregation.data.slot not within ATTESTATION_PROPAGATION_SLOT_RANGE"
|
||||
return false
|
||||
|
||||
|
|
|
@ -14,9 +14,11 @@ import
|
|||
chronicles, stew/[byteutils], json_serialization/std/sets,
|
||||
# Internal
|
||||
./spec/[beaconstate, datatypes, crypto, digest, helpers],
|
||||
./extras, ./block_pool, ./block_pools/candidate_chains, ./beacon_node_types,
|
||||
./block_pool, ./block_pools/candidate_chains, ./beacon_node_types,
|
||||
./fork_choice/fork_choice
|
||||
|
||||
export beacon_node_types
|
||||
|
||||
logScope: topics = "attpool"
|
||||
|
||||
proc init*(T: type AttestationPool, blockPool: BlockPool): T =
|
||||
|
@ -32,7 +34,10 @@ proc init*(T: type AttestationPool, blockPool: BlockPool): T =
|
|||
blockPool.tmpState,
|
||||
).get()
|
||||
|
||||
# Feed fork choice with unfinalized history
|
||||
# Feed fork choice with unfinalized history - during startup, block pool only
|
||||
# keeps track of a single history so we just need to follow it
|
||||
doAssert blockPool.heads.len == 1, "Init only supports a single history"
|
||||
|
||||
var blocks: seq[BlockRef]
|
||||
var cur = blockPool.head.blck
|
||||
while cur != blockPool.finalizedHead.blck:
|
||||
|
@ -66,25 +71,6 @@ proc init*(T: type AttestationPool, blockPool: BlockPool): T =
|
|||
forkChoice: forkChoice
|
||||
)
|
||||
|
||||
proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) =
|
||||
## Combine the signature and participation bitfield, with the assumption that
|
||||
## the same data is being signed - if the signatures overlap, they are not
|
||||
## combined.
|
||||
# TODO: Exported only for testing, all usage are internals
|
||||
|
||||
doAssert tgt.data == src.data
|
||||
|
||||
# In a BLS aggregate signature, one needs to count how many times a
|
||||
# particular public key has been added - since we use a single bit per key, we
|
||||
# can only it once, thus we can never combine signatures that overlap already!
|
||||
if not tgt.aggregation_bits.overlaps(src.aggregation_bits):
|
||||
tgt.aggregation_bits.combine(src.aggregation_bits)
|
||||
|
||||
if skipBlsValidation notin flags:
|
||||
tgt.signature.aggregate(src.signature)
|
||||
else:
|
||||
trace "Ignoring overlapping attestations"
|
||||
|
||||
proc slotIndex(
|
||||
pool: var AttestationPool, state: BeaconState, attestationSlot: Slot): int =
|
||||
## Grow and garbage collect pool, returning the deque index of the slot
|
||||
|
@ -139,10 +125,10 @@ proc slotIndex(
|
|||
int(attestationSlot - pool.startingSlot)
|
||||
|
||||
func processAttestation(
|
||||
pool: var AttestationPool, state: BeaconState,
|
||||
participants: seq[ValidatorIndex], block_root: Eth2Digest, target_epoch: Epoch) =
|
||||
pool: var AttestationPool, participants: HashSet[ValidatorIndex],
|
||||
block_root: Eth2Digest, target_epoch: Epoch) =
|
||||
# Add attestation votes to fork choice
|
||||
for validator in participants:
|
||||
# ForkChoice v2
|
||||
pool.forkChoice.process_attestation(validator, block_root, target_epoch)
|
||||
|
||||
func addUnresolved(pool: var AttestationPool, attestation: Attestation) =
|
||||
|
@ -152,6 +138,9 @@ func addUnresolved(pool: var AttestationPool, attestation: Attestation) =
|
|||
)
|
||||
|
||||
proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attestation) =
|
||||
logScope:
|
||||
attestation = shortLog(attestation)
|
||||
|
||||
doAssert blck.root == attestation.data.beacon_block_root
|
||||
|
||||
# TODO Which state should we use to validate the attestation? It seems
|
||||
|
@ -163,7 +152,6 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
|||
# we should use isValidAttestationSlot instead
|
||||
if blck.slot > attestation.data.slot:
|
||||
notice "Invalid attestation (too new!)",
|
||||
attestation = shortLog(attestation),
|
||||
blockSlot = shortLog(blck.slot)
|
||||
return
|
||||
|
||||
|
@ -172,7 +160,6 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
|||
# though they no longer are relevant for finalization - let's clear
|
||||
# these out
|
||||
debug "Old attestation",
|
||||
attestation = shortLog(attestation),
|
||||
startingSlot = pool.startingSlot
|
||||
return
|
||||
|
||||
|
@ -181,14 +168,8 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
|||
# return
|
||||
|
||||
# Check that the attestation is indeed valid
|
||||
# TODO: we might want to split checks that depend
|
||||
# on the state and those that don't to cheaply
|
||||
# discard invalid attestations before rewinding state.
|
||||
if not isValidAttestationTargetEpoch(
|
||||
attestation.data.target.epoch, attestation.data):
|
||||
notice "Invalid attestation",
|
||||
attestation = shortLog(attestation),
|
||||
current_epoch = attestation.data.slot.compute_epoch_at_slot
|
||||
if (let v = check_attestation_slot_target(attestation.data); v.isErr):
|
||||
debug "Invalid attestation", err = v.error
|
||||
return
|
||||
|
||||
# Get a temporary state at the (block, slot) targeted by the attestation
|
||||
|
@ -209,8 +190,8 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
|||
validation = Validation(
|
||||
aggregation_bits: attestation.aggregation_bits,
|
||||
aggregate_signature: attestation.signature)
|
||||
participants = toSeq(items(get_attesting_indices(
|
||||
state, attestation.data, validation.aggregation_bits, cache)))
|
||||
participants = get_attesting_indices(
|
||||
state, attestation.data, validation.aggregation_bits, cache)
|
||||
|
||||
var found = false
|
||||
for a in attestationsSeen.attestations.mitems():
|
||||
|
@ -240,7 +221,7 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
|||
|
||||
a.validations.add(validation)
|
||||
pool.processAttestation(
|
||||
state, participants, a.blck.root, attestation.data.target.epoch)
|
||||
participants, a.blck.root, attestation.data.target.epoch)
|
||||
|
||||
info "Attestation resolved",
|
||||
attestation = shortLog(attestation),
|
||||
|
@ -259,7 +240,7 @@ proc addResolved(pool: var AttestationPool, blck: BlockRef, attestation: Attesta
|
|||
validations: @[validation]
|
||||
))
|
||||
pool.processAttestation(
|
||||
state, participants, blck.root, attestation.data.target.epoch)
|
||||
participants, blck.root, attestation.data.target.epoch)
|
||||
|
||||
info "Attestation resolved",
|
||||
attestation = shortLog(attestation),
|
||||
|
@ -383,9 +364,11 @@ proc getAttestationsForBlock*(pool: AttestationPool,
|
|||
# state should they be validated, if at all?
|
||||
# TODO we're checking signatures here every time which is very slow and we don't want
|
||||
# to include a broken attestation
|
||||
if not check_attestation(state, attestation, {}, cache):
|
||||
if (let v = check_attestation(state, attestation, {}, cache); v.isErr):
|
||||
warn "Attestation no longer validates...",
|
||||
attestation = shortLog(attestation)
|
||||
attestation = shortLog(attestation),
|
||||
err = v.error
|
||||
|
||||
continue
|
||||
|
||||
for v in a.validations[1..^1]:
|
||||
|
|
|
@ -17,28 +17,18 @@ type
|
|||
#
|
||||
# #############################################
|
||||
Validation* = object
|
||||
## Validations collect a set of signatures for a distict attestation - in
|
||||
## eth2, a single bit is used to keep track of which signatures have been
|
||||
## added to the aggregate meaning that only non-overlapping aggregates may
|
||||
## be further combined.
|
||||
aggregation_bits*: CommitteeValidatorsBits
|
||||
aggregate_signature*: ValidatorSig
|
||||
|
||||
# Per Danny as of 2018-12-21:
|
||||
# Yeah, you can do any linear combination of signatures. but you have to
|
||||
# remember the linear combination of pubkeys that constructed
|
||||
# if you have two instances of a signature from pubkey p, then you need 2*p
|
||||
# in the group pubkey because the attestation bitlist is only 1 bit per
|
||||
# pubkey right now, attestations do not support this it could be extended to
|
||||
# support N overlaps up to N times per pubkey if we had N bits per validator
|
||||
# instead of 1
|
||||
# We are shying away from this for the time being. If there end up being
|
||||
# substantial difficulties in network layer aggregation, then adding bits to
|
||||
# aid in supporting overlaps is one potential solution
|
||||
|
||||
AttestationEntry* = object
|
||||
## Each entry holds the known signatures for a particular, distinct vote
|
||||
data*: AttestationData
|
||||
blck*: BlockRef
|
||||
validations*: seq[Validation] ## \
|
||||
## Instead of aggregating the signatures eagerly, we simply dump them in
|
||||
## this seq and aggregate only when needed
|
||||
## TODO there are obvious caching opportunities here..
|
||||
validations*: seq[Validation]
|
||||
|
||||
AttestationsSeen* = object
|
||||
attestations*: seq[AttestationEntry] ## \
|
||||
|
@ -54,11 +44,11 @@ type
|
|||
tries*: int
|
||||
|
||||
AttestationPool* = object
|
||||
## The attestation pool keeps all attestations that are known to the
|
||||
## client - each attestation counts as votes towards the fork choice
|
||||
## rule that determines which block we consider to be the head. The pool
|
||||
## contains both votes that have been included in the chain and those that
|
||||
## have not.
|
||||
## The attestation pool keeps track of all attestations that potentially
|
||||
## could be added to a block during block production.
|
||||
## These attestations also contribute to the fork choice, which combines
|
||||
## "free" attestations with those found in past blocks - these votes
|
||||
## are tracked separately in the fork choice.
|
||||
|
||||
mapSlotsToAttestations*: Deque[AttestationsSeen] ## \
|
||||
## We keep one item per slot such that indexing matches slot number
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import
|
||||
extras, beacon_chain_db,
|
||||
stew/results,
|
||||
spec/[crypto, datatypes, digest, helpers, presets]
|
||||
spec/[crypto, datatypes, digest, presets, validator]
|
||||
|
||||
|
||||
import
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import
|
||||
deques, tables, hashes, options, strformat,
|
||||
std/[deques, tables, hashes, options, strformat],
|
||||
chronos, web3, web3/ethtypes as web3Types, json, chronicles,
|
||||
eth/common/eth_types, eth/async_utils,
|
||||
spec/[datatypes, digest, crypto, beaconstate, helpers],
|
||||
spec/[datatypes, digest, crypto, beaconstate, helpers, validator],
|
||||
network_metadata, merkle_minimal
|
||||
|
||||
from times import epochTime
|
||||
|
@ -506,7 +506,8 @@ proc createBeaconStateAux(preset: RuntimePreset,
|
|||
eth1Block.voteData.block_hash,
|
||||
eth1Block.timestamp.uint64,
|
||||
deposits, {})
|
||||
let activeValidators = count_active_validators(result[], GENESIS_EPOCH, StateCache())
|
||||
var cache = StateCache()
|
||||
let activeValidators = count_active_validators(result[], GENESIS_EPOCH, cache)
|
||||
eth1Block.knownGoodDepositsCount = some activeValidators
|
||||
|
||||
proc createBeaconState(m: MainchainMonitor, eth1Block: Eth1Block): BeaconStateRef =
|
||||
|
|
|
@ -513,185 +513,116 @@ func get_indexed_attestation*(state: BeaconState, attestation: TrustedAttestatio
|
|||
|
||||
# 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
|
||||
|
||||
true
|
||||
|
||||
# TODO remove/merge with p2p-interface validation
|
||||
proc isValidAttestationTargetEpoch*(
|
||||
state_epoch: Epoch, 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)
|
||||
|
||||
# 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_epoch) or
|
||||
data.target.epoch == state_epoch):
|
||||
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
|
||||
|
||||
func check_attestation_slot_target*(data: AttestationData): Result[void, cstring] =
|
||||
if not (data.target.epoch == compute_epoch_at_slot(data.slot)):
|
||||
return err("Target epoch doesn't match attestation slot")
|
||||
|
||||
ok()
|
||||
|
||||
func check_attestation_target_epoch*(
|
||||
data: AttestationData, current_epoch: Epoch): Result[void, cstring] =
|
||||
if not (data.target.epoch == get_previous_epoch(current_epoch) or
|
||||
data.target.epoch == current_epoch):
|
||||
return err("Target epoch not current or previous epoch")
|
||||
|
||||
ok()
|
||||
|
||||
func check_attestation_inclusion*(data: AttestationData,
|
||||
current_slot: Slot): Result[void, cstring] =
|
||||
if not (data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= current_slot):
|
||||
return err("Attestation too new")
|
||||
|
||||
if not (current_slot <= data.slot + SLOTS_PER_EPOCH):
|
||||
return err("Attestation too old")
|
||||
|
||||
ok()
|
||||
|
||||
func check_attestation_index*(
|
||||
data: AttestationData, committees_per_slot: uint64): Result[void, cstring] =
|
||||
if not (data.index < committees_per_slot):
|
||||
return err("Data index exceeds committee count")
|
||||
|
||||
ok()
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/specs/phase0/beacon-chain.md#attestations
|
||||
proc check_attestation*(
|
||||
state: BeaconState, attestation: SomeAttestation, flags: UpdateFlags,
|
||||
stateCache: var StateCache): bool =
|
||||
cache: var StateCache): Result[void, cstring] =
|
||||
## 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"
|
||||
? check_attestation_target_epoch(data, state.get_current_epoch())
|
||||
? check_attestation_slot_target(data)
|
||||
? check_attestation_inclusion(data, state.slot)
|
||||
? check_attestation_index(
|
||||
data,
|
||||
get_committee_count_per_slot(state, data.target.epoch, cache))
|
||||
|
||||
if not isValidAttestationTargetEpoch(state.get_current_epoch(), data):
|
||||
# Logging in isValidAttestationTargetEpoch
|
||||
return
|
||||
let committee_len = get_beacon_committee_len(
|
||||
state, data.slot, data.index.CommitteeIndex, cache)
|
||||
|
||||
if not isValidAttestationSlot(data.slot, stateSlot):
|
||||
# Logging in isValidAttestationSlot
|
||||
return
|
||||
|
||||
let committees_per_slot =
|
||||
get_committee_count_per_slot(
|
||||
state, data.target.epoch, stateCache)
|
||||
if not (data.index < committees_per_slot):
|
||||
warn "Data index exceeds committee count",
|
||||
committee_count = committees_per_slot
|
||||
return
|
||||
|
||||
if not (data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot):
|
||||
warn "Attestation too new"
|
||||
return
|
||||
|
||||
if not (state.slot <= data.slot + SLOTS_PER_EPOCH):
|
||||
warn "Attestation too old"
|
||||
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 attestation.aggregation_bits.lenu64 != committee_len:
|
||||
return err("Inconsistent aggregation and committee length")
|
||||
|
||||
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
|
||||
if not (data.source == state.current_justified_checkpoint):
|
||||
return err("FFG data not matching current justified epoch")
|
||||
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 (data.source == state.previous_justified_checkpoint):
|
||||
return err("FFG data not matching previous justified epoch")
|
||||
|
||||
if not is_valid_indexed_attestation(
|
||||
state, get_indexed_attestation(state, attestation, stateCache), flags):
|
||||
warn "process_attestation: signature or bitfields incorrect"
|
||||
return
|
||||
state, get_indexed_attestation(state, attestation, cache), flags):
|
||||
return err("signature or bitfields incorrect")
|
||||
|
||||
true
|
||||
ok()
|
||||
|
||||
proc process_attestation*(
|
||||
state: var BeaconState, attestation: SomeAttestation, flags: UpdateFlags,
|
||||
stateCache: var StateCache): Result[void, cstring] {.nbench.} =
|
||||
cache: 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)
|
||||
let proposer_index = get_beacon_proposer_index(state, cache)
|
||||
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,
|
||||
)
|
||||
? check_attestation(state, attestation, flags, cache)
|
||||
|
||||
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)
|
||||
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,
|
||||
)
|
||||
|
||||
ok()
|
||||
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, cache).len
|
||||
state.current_epoch_attestations.add(pending_attestation)
|
||||
else:
|
||||
err("process_attestation: check_attestation failed")
|
||||
trace "process_attestation: previous_epoch_attestations.add",
|
||||
attestation = shortLog(attestation),
|
||||
pending_attestation = pending_attestation,
|
||||
indices = get_attesting_indices(
|
||||
state, attestation.data, attestation.aggregation_bits, cache).len
|
||||
state.previous_epoch_attestations.add(pending_attestation)
|
||||
|
||||
ok()
|
||||
|
||||
func makeAttestationData*(
|
||||
state: BeaconState, slot: Slot, committee_index: uint64,
|
||||
|
|
|
@ -650,15 +650,19 @@ func shortLog*(v: DepositData): auto =
|
|||
signature: shortLog(v.signature)
|
||||
)
|
||||
|
||||
func shortLog*(v: Checkpoint): auto =
|
||||
(
|
||||
epoch: shortLog(v.epoch),
|
||||
root: shortLog(v.root),
|
||||
)
|
||||
|
||||
func shortLog*(v: AttestationData): auto =
|
||||
(
|
||||
slot: shortLog(v.slot),
|
||||
index: v.index,
|
||||
beacon_block_root: shortLog(v.beacon_block_root),
|
||||
source_epoch: shortLog(v.source.epoch),
|
||||
source_root: shortLog(v.source.root),
|
||||
target_epoch: shortLog(v.target.epoch),
|
||||
target_root: shortLog(v.target.root)
|
||||
source: shortLog(v.source),
|
||||
target: shortLog(v.target),
|
||||
)
|
||||
|
||||
func shortLog*(v: SomeAttestation): auto =
|
||||
|
@ -673,6 +677,7 @@ chronicles.formatIt Epoch: it.shortLog
|
|||
chronicles.formatIt BeaconBlock: it.shortLog
|
||||
chronicles.formatIt AttestationData: it.shortLog
|
||||
chronicles.formatIt Attestation: it.shortLog
|
||||
chronicles.formatIt Checkpoint: it.shortLog
|
||||
|
||||
import json_serialization
|
||||
export json_serialization
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
import
|
||||
# Standard lib
|
||||
std/[math, sequtils, tables],
|
||||
std/[math, tables],
|
||||
# Third-party
|
||||
stew/endians2,
|
||||
# Internal
|
||||
|
@ -56,18 +56,6 @@ func is_active_validator*(validator: Validator, epoch: Epoch): bool =
|
|||
### Check if ``validator`` is active
|
||||
validator.activation_epoch <= epoch and epoch < validator.exit_epoch
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_active_validator_indices
|
||||
func count_active_validators*(state: BeaconState,
|
||||
epoch: Epoch,
|
||||
cache: StateCache): uint64 =
|
||||
if epoch in cache.shuffled_active_validator_indices:
|
||||
try:
|
||||
cache.shuffled_active_validator_indices[epoch].lenu64
|
||||
except KeyError:
|
||||
raiseAssert "just checked"
|
||||
else:
|
||||
countIt(state.validators, is_active_validator(it, epoch)).uint64
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_active_validator_indices
|
||||
func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
||||
seq[ValidatorIndex] =
|
||||
|
@ -76,28 +64,6 @@ func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
|||
if is_active_validator(val, epoch):
|
||||
result.add idx.ValidatorIndex
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_committee_count_per_slot
|
||||
func get_committee_count_per_slot*(num_active_validators: uint64): uint64 =
|
||||
clamp(
|
||||
num_active_validators div SLOTS_PER_EPOCH div TARGET_COMMITTEE_SIZE,
|
||||
1'u64, MAX_COMMITTEES_PER_SLOT)
|
||||
|
||||
func get_committee_count_per_slot*(state: BeaconState,
|
||||
epoch: Epoch,
|
||||
cache: StateCache): uint64 =
|
||||
# Return the number of committees at ``slot``.
|
||||
|
||||
# TODO this is mostly used in for loops which have indexes which then need to
|
||||
# be converted to CommitteeIndex types for get_beacon_committee(...); replace
|
||||
# with better and more type-safe use pattern, probably beginning with using a
|
||||
# CommitteeIndex return type here.
|
||||
let
|
||||
active_validator_count = count_active_validators(state, epoch, cache)
|
||||
result = get_committee_count_per_slot(active_validator_count)
|
||||
|
||||
# Otherwise, get_beacon_committee(...) cannot access some committees.
|
||||
doAssert (SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT) >= uint64(result)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_current_epoch
|
||||
func get_current_epoch*(state: BeaconState): Epoch =
|
||||
# Return the current epoch.
|
||||
|
@ -123,12 +89,6 @@ func bytes_to_uint64*(data: openarray[byte]): uint64 =
|
|||
func uint_to_bytes8*(x: uint64): array[8, byte] =
|
||||
x.toBytesLE()
|
||||
|
||||
func int_to_bytes1*(x: int): array[1, byte] =
|
||||
doAssert x >= 0
|
||||
doAssert x < 256
|
||||
|
||||
result[0] = x.byte
|
||||
|
||||
func uint_to_bytes4*(x: uint64): array[4, byte] =
|
||||
doAssert x < 2'u64^32
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
strformat,
|
||||
datatypes, helpers
|
||||
std/[strformat, sets],
|
||||
./datatypes, ./helpers, ./validator
|
||||
|
||||
const
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#topics-and-messages
|
||||
|
@ -92,3 +92,22 @@ func getAttestationTopic*(forkDigest: ForkDigest,
|
|||
compute_subnet_for_attestation(
|
||||
get_committee_count_per_slot(num_active_validators),
|
||||
attestation.data.slot, attestation.data.index.CommitteeIndex))
|
||||
|
||||
func get_committee_assignments*(
|
||||
state: BeaconState, epoch: Epoch,
|
||||
validator_indices: HashSet[ValidatorIndex]):
|
||||
seq[tuple[subnetIndex: uint64, slot: Slot]] =
|
||||
var cache = StateCache()
|
||||
|
||||
let
|
||||
committees_per_slot = get_committee_count_per_slot(state, epoch, cache)
|
||||
start_slot = compute_start_slot_at_epoch(epoch)
|
||||
|
||||
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
|
||||
for index in 0'u64 ..< committees_per_slot:
|
||||
let idx = index.CommitteeIndex
|
||||
if not disjoint(validator_indices,
|
||||
get_beacon_committee(state, slot, idx, cache).toHashSet):
|
||||
result.add(
|
||||
(compute_subnet_for_attestation(committees_per_slot, slot, idx),
|
||||
slot))
|
||||
|
|
|
@ -201,25 +201,7 @@ proc state_transition*(
|
|||
## it is safe to use `noRollback` and leave it broken, else the state
|
||||
## object should be rolled back to a consistent state. If the transition fails
|
||||
## before the state has been updated, `rollback` will not be called.
|
||||
#
|
||||
# TODO this function can be written with a loop inside to handle all empty
|
||||
# slots up to the slot of the new_block - but then again, why not eagerly
|
||||
# update the state as time passes? Something to ponder...
|
||||
# One reason to keep it this way is that you need to look ahead if you're
|
||||
# the block proposer, though in reality we only need a partial update for
|
||||
# that ===> Implemented as process_slots
|
||||
# TODO There's a discussion about what this function should do, and when:
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/284
|
||||
|
||||
# TODO check to which extent this copy can be avoided (considering forks etc),
|
||||
# for now, it serves as a reminder that we need to handle invalid blocks
|
||||
# somewhere..
|
||||
# many functions will mutate `state` partially without rolling back
|
||||
# the changes in case of failure (look out for `var BeaconState` and
|
||||
# bool return values...)
|
||||
doAssert not rollback.isNil, "use noRollback if it's ok to mess up state"
|
||||
doAssert stateCache.shuffled_active_validator_indices.hasKey(
|
||||
state.data.get_current_epoch())
|
||||
|
||||
if not process_slots(state, signedBlock.message.slot, flags):
|
||||
rollback(state)
|
||||
|
@ -229,8 +211,6 @@ proc state_transition*(
|
|||
# by the block proposer. Every actor in the network will update its state
|
||||
# according to the contents of this block - but first they will validate
|
||||
# that the block is sane.
|
||||
# TODO what should happen if block processing fails?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/293
|
||||
if skipBLSValidation in flags or
|
||||
verify_block_signature(state.data, signedBlock):
|
||||
|
||||
|
@ -265,9 +245,6 @@ proc state_transition*(
|
|||
# and fuzzing code should always be coming from blockpool which should
|
||||
# always be providing cache or equivalent
|
||||
var cache = StateCache()
|
||||
cache.shuffled_active_validator_indices[state.data.get_current_epoch()] =
|
||||
get_shuffled_active_validator_indices(
|
||||
state.data, state.data.get_current_epoch())
|
||||
state_transition(preset, state, signedBlock, cache, flags, rollback)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#preparing-for-a-beaconblock
|
||||
|
|
|
@ -61,14 +61,9 @@ func get_total_active_balance*(state: BeaconState, cache: var StateCache): Gwei
|
|||
|
||||
let
|
||||
epoch = state.get_current_epoch()
|
||||
try:
|
||||
if epoch notin cache.shuffled_active_validator_indices:
|
||||
cache.shuffled_active_validator_indices[epoch] =
|
||||
get_shuffled_active_validator_indices(state, epoch)
|
||||
|
||||
get_total_balance(state, cache.shuffled_active_validator_indices[epoch])
|
||||
except KeyError:
|
||||
raiseAssert("get_total_active_balance(): cache always filled before usage")
|
||||
get_total_balance(
|
||||
state, cache.get_shuffled_active_validator_indices(state, epoch))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
func get_matching_source_attestations(state: BeaconState,
|
||||
|
|
|
@ -9,17 +9,9 @@
|
|||
|
||||
import
|
||||
# Standard library
|
||||
sets,
|
||||
std/sets,
|
||||
# Internals
|
||||
./datatypes, ./digest, ./beaconstate
|
||||
|
||||
# Logging utilities
|
||||
# --------------------------------------------------------
|
||||
|
||||
# TODO: gather all logging utilities
|
||||
# from crypto, digest, etc in a single file
|
||||
func shortLog*(x: Checkpoint): string =
|
||||
"(epoch: " & $x.epoch & ", root: \"" & shortLog(x.root) & "\")"
|
||||
./datatypes, ./beaconstate
|
||||
|
||||
# Helpers used in epoch transition and trace-level block transition
|
||||
# --------------------------------------------------------
|
||||
|
@ -27,19 +19,19 @@ func shortLog*(x: Checkpoint): string =
|
|||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
func get_attesting_indices*(
|
||||
state: BeaconState, attestations: openArray[PendingAttestation],
|
||||
stateCache: var StateCache): HashSet[ValidatorIndex] =
|
||||
cache: var StateCache): HashSet[ValidatorIndex] =
|
||||
# This is part of get_unslashed_attesting_indices(...) in spec.
|
||||
# Exported bceause of external trace-level chronicles logging.
|
||||
result = initHashSet[ValidatorIndex]()
|
||||
for a in attestations:
|
||||
result.incl get_attesting_indices(
|
||||
state, a.data, a.aggregation_bits, stateCache)
|
||||
state, a.data, a.aggregation_bits, cache)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#helper-functions-1
|
||||
func get_unslashed_attesting_indices*(
|
||||
state: BeaconState, attestations: openArray[PendingAttestation],
|
||||
stateCache: var StateCache): HashSet[ValidatorIndex] =
|
||||
result = get_attesting_indices(state, attestations, stateCache)
|
||||
cache: var StateCache): HashSet[ValidatorIndex] =
|
||||
result = get_attesting_indices(state, attestations, cache)
|
||||
for index in result:
|
||||
if state.validators[index].slashed:
|
||||
result.excl index
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
algorithm, options, sequtils, math, tables, sets,
|
||||
./datatypes, ./digest, ./helpers, ./network
|
||||
algorithm, options, sequtils, math, tables,
|
||||
./datatypes, ./digest, ./helpers
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_shuffled_index
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_committee
|
||||
|
@ -46,10 +46,10 @@ func get_shuffled_seq*(seed: Eth2Digest,
|
|||
pivot_buffer[0..31] = seed.data
|
||||
source_buffer[0..31] = seed.data
|
||||
|
||||
for round in 0 ..< SHUFFLE_ROUND_COUNT.int:
|
||||
let round_bytes1 = int_to_bytes1(round)[0]
|
||||
pivot_buffer[32] = round_bytes1
|
||||
source_buffer[32] = round_bytes1
|
||||
static: doAssert SHUFFLE_ROUND_COUNT < uint8.high
|
||||
for round in 0'u8 ..< SHUFFLE_ROUND_COUNT.uint8:
|
||||
pivot_buffer[32] = round
|
||||
source_buffer[32] = round
|
||||
|
||||
# Only one pivot per round.
|
||||
let pivot =
|
||||
|
@ -93,14 +93,48 @@ func get_shuffled_active_validator_indices*(state: BeaconState, epoch: Epoch):
|
|||
active_validator_indices[it])
|
||||
|
||||
func get_shuffled_active_validator_indices*(
|
||||
state: BeaconState, epoch: Epoch, cache: var StateCache):
|
||||
seq[ValidatorIndex] =
|
||||
try:
|
||||
cache.shuffled_active_validator_indices[epoch]
|
||||
except KeyError:
|
||||
let validator_indices = get_shuffled_active_validator_indices(state, epoch)
|
||||
cache.shuffled_active_validator_indices[epoch] = validator_indices
|
||||
validator_indices
|
||||
cache: var StateCache, state: BeaconState, epoch: Epoch):
|
||||
var seq[ValidatorIndex] =
|
||||
# `cache` comes first because of nim's borrowing rules for the `var` return -
|
||||
# the `var` returns avoids copying the validator set.
|
||||
cache.shuffled_active_validator_indices.withValue(epoch, validator_indices) do:
|
||||
return validator_indices[]
|
||||
do:
|
||||
let indices = get_shuffled_active_validator_indices(state, epoch)
|
||||
return cache.shuffled_active_validator_indices.mgetOrPut(epoch, indices)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_active_validator_indices
|
||||
func count_active_validators*(state: BeaconState,
|
||||
epoch: Epoch,
|
||||
cache: var StateCache): uint64 =
|
||||
cache.get_shuffled_active_validator_indices(state, epoch).lenu64
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_committee_count_per_slot
|
||||
func get_committee_count_per_slot*(num_active_validators: uint64): uint64 =
|
||||
clamp(
|
||||
num_active_validators div SLOTS_PER_EPOCH div TARGET_COMMITTEE_SIZE,
|
||||
1'u64, MAX_COMMITTEES_PER_SLOT)
|
||||
|
||||
func get_committee_count_per_slot*(state: BeaconState,
|
||||
epoch: Epoch,
|
||||
cache: var StateCache): uint64 =
|
||||
# Return the number of committees at ``slot``.
|
||||
|
||||
# TODO this is mostly used in for loops which have indexes which then need to
|
||||
# be converted to CommitteeIndex types for get_beacon_committee(...); replace
|
||||
# with better and more type-safe use pattern, probably beginning with using a
|
||||
# CommitteeIndex return type here.
|
||||
let
|
||||
active_validator_count = count_active_validators(state, epoch, cache)
|
||||
result = get_committee_count_per_slot(active_validator_count)
|
||||
|
||||
# Otherwise, get_beacon_committee(...) cannot access some committees.
|
||||
doAssert (SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT) >= uint64(result)
|
||||
|
||||
func get_committee_count_per_slot*(state: BeaconState,
|
||||
slot: Slot,
|
||||
cache: var StateCache): uint64 =
|
||||
get_committee_count_per_slot(state, slot.compute_epoch_at_slot, cache)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_previous_epoch
|
||||
func get_previous_epoch*(current_epoch: Epoch): Epoch =
|
||||
|
@ -115,28 +149,47 @@ func get_previous_epoch*(state: BeaconState): Epoch =
|
|||
get_previous_epoch(get_current_epoch(state))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_committee
|
||||
func compute_committee(indices: seq[ValidatorIndex], seed: Eth2Digest,
|
||||
func compute_committee(shuffled_indices: seq[ValidatorIndex],
|
||||
index: uint64, count: uint64): seq[ValidatorIndex] =
|
||||
## Return the committee corresponding to ``indices``, ``seed``, ``index``,
|
||||
## and committee ``count``.
|
||||
## In this version, we pass in the shuffled indices meaning we no longer need
|
||||
## the seed.
|
||||
|
||||
let
|
||||
active_validators = shuffled_indices.len.uint64
|
||||
start = (active_validators * index) div count
|
||||
endIdx = (active_validators * (index + 1)) div count
|
||||
|
||||
# These assertions from compute_shuffled_index(...)
|
||||
doAssert endIdx <= active_validators
|
||||
doAssert active_validators <= 2'u64^40
|
||||
|
||||
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
||||
# Here, get_beacon_committee() gets the shuffled version.
|
||||
try:
|
||||
shuffled_indices[start.int .. (endIdx.int-1)]
|
||||
except KeyError:
|
||||
raiseAssert("Cached entries are added before use")
|
||||
|
||||
func compute_committee_len(active_validators: uint64,
|
||||
index: uint64, count: uint64): uint64 =
|
||||
## Return the committee corresponding to ``indices``, ``seed``, ``index``,
|
||||
## and committee ``count``.
|
||||
|
||||
# indices only used here for its length, or for the shuffled version,
|
||||
# so unlike spec, pass the shuffled version in directly.
|
||||
let
|
||||
start = (len(indices).uint64 * index) div count
|
||||
endIdx = (len(indices).uint64 * (index + 1)) div count
|
||||
start = (active_validators * index) div count
|
||||
endIdx = (active_validators * (index + 1)) div count
|
||||
|
||||
# These assertions from compute_shuffled_index(...)
|
||||
let index_count = indices.len().uint64
|
||||
doAssert endIdx <= index_count
|
||||
doAssert index_count <= 2'u64^40
|
||||
doAssert endIdx <= active_validators
|
||||
doAssert active_validators <= 2'u64^40
|
||||
|
||||
# In spec, this calls get_shuffled_index() every time, but that's wasteful
|
||||
# Here, get_beacon_committee() gets the shuffled version.
|
||||
try:
|
||||
indices[start.int .. (endIdx.int-1)]
|
||||
except KeyError:
|
||||
raiseAssert("Cached entries are added before use")
|
||||
endIdx - start
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_beacon_committee
|
||||
func get_beacon_committee*(
|
||||
|
@ -145,28 +198,28 @@ func get_beacon_committee*(
|
|||
# Return the beacon committee at ``slot`` for ``index``.
|
||||
let
|
||||
epoch = compute_epoch_at_slot(slot)
|
||||
committees_per_slot = get_committee_count_per_slot(state, epoch, cache)
|
||||
compute_committee(
|
||||
cache.get_shuffled_active_validator_indices(state, epoch),
|
||||
(slot mod SLOTS_PER_EPOCH) * committees_per_slot +
|
||||
index.uint64,
|
||||
committees_per_slot * SLOTS_PER_EPOCH
|
||||
)
|
||||
|
||||
# This is a somewhat more fragile, but high-ROI, caching setup --
|
||||
# get_active_validator_indices() is slow to run in a loop and only
|
||||
# changes once per epoch. It is not, in the general case, possible
|
||||
# to precompute these arbitrarily far out so still need to pick up
|
||||
# missing cases here.
|
||||
if epoch notin cache.shuffled_active_validator_indices:
|
||||
cache.shuffled_active_validator_indices[epoch] =
|
||||
get_shuffled_active_validator_indices(state, epoch)
|
||||
func get_beacon_committee_len*(
|
||||
state: BeaconState, slot: Slot, index: CommitteeIndex,
|
||||
cache: var StateCache): uint64 =
|
||||
# Return the number of members in the beacon committee at ``slot`` for ``index``.
|
||||
let
|
||||
epoch = compute_epoch_at_slot(slot)
|
||||
committees_per_slot = get_committee_count_per_slot(state, epoch, cache)
|
||||
|
||||
try:
|
||||
let committees_per_slot = get_committee_count_per_slot(
|
||||
cache.shuffled_active_validator_indices[epoch].lenu64)
|
||||
compute_committee(
|
||||
cache.shuffled_active_validator_indices[epoch],
|
||||
get_seed(state, epoch, DOMAIN_BEACON_ATTESTER),
|
||||
(slot mod SLOTS_PER_EPOCH) * committees_per_slot +
|
||||
index.uint64,
|
||||
committees_per_slot * SLOTS_PER_EPOCH
|
||||
)
|
||||
except KeyError:
|
||||
raiseAssert "values are added to cache before using them"
|
||||
compute_committee_len(
|
||||
count_active_validators(state, epoch, cache),
|
||||
(slot mod SLOTS_PER_EPOCH) * committees_per_slot +
|
||||
index.uint64,
|
||||
committees_per_slot * SLOTS_PER_EPOCH
|
||||
)
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#compute_shuffled_index
|
||||
func compute_shuffled_index(
|
||||
|
@ -184,10 +237,9 @@ func compute_shuffled_index(
|
|||
|
||||
# Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf)
|
||||
# See the 'generalized domain' algorithm on page 3
|
||||
for current_round in 0 ..< SHUFFLE_ROUND_COUNT.int:
|
||||
let round_bytes1 = int_to_bytes1(current_round)[0]
|
||||
pivot_buffer[32] = round_bytes1
|
||||
source_buffer[32] = round_bytes1
|
||||
for current_round in 0'u8 ..< SHUFFLE_ROUND_COUNT.uint8:
|
||||
pivot_buffer[32] = current_round
|
||||
source_buffer[32] = current_round
|
||||
|
||||
let
|
||||
# If using multiple indices, can amortize this
|
||||
|
@ -237,36 +289,26 @@ func compute_proposer_index(state: BeaconState, indices: seq[ValidatorIndex],
|
|||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_beacon_proposer_index
|
||||
func get_beacon_proposer_index*(state: BeaconState, cache: var StateCache, slot: Slot):
|
||||
Option[ValidatorIndex] =
|
||||
try:
|
||||
if slot in cache.beacon_proposer_indices:
|
||||
return cache.beacon_proposer_indices[slot]
|
||||
except KeyError:
|
||||
raiseAssert("Cached entries are added before use")
|
||||
cache.beacon_proposer_indices.withValue(slot, proposer) do:
|
||||
return proposer[]
|
||||
do:
|
||||
|
||||
# Return the beacon proposer index at the current slot.
|
||||
let epoch = get_current_epoch(state)
|
||||
# Return the beacon proposer index at the current slot.
|
||||
let epoch = get_current_epoch(state)
|
||||
|
||||
var buffer: array[32 + 8, byte]
|
||||
buffer[0..31] = get_seed(state, epoch, DOMAIN_BEACON_PROPOSER).data
|
||||
buffer[32..39] = uint_to_bytes8(slot.uint64)
|
||||
var buffer: array[32 + 8, byte]
|
||||
buffer[0..31] = get_seed(state, epoch, DOMAIN_BEACON_PROPOSER).data
|
||||
buffer[32..39] = uint_to_bytes8(slot.uint64)
|
||||
|
||||
# TODO fixme; should only be run once per slot and cached
|
||||
# There's exactly one beacon proposer per slot.
|
||||
if epoch notin cache.shuffled_active_validator_indices:
|
||||
cache.shuffled_active_validator_indices[epoch] =
|
||||
get_shuffled_active_validator_indices(state, epoch)
|
||||
# There's exactly one beacon proposer per slot.
|
||||
|
||||
try:
|
||||
let
|
||||
seed = eth2digest(buffer)
|
||||
indices =
|
||||
sorted(cache.shuffled_active_validator_indices[epoch], system.cmp)
|
||||
sorted(cache.get_shuffled_active_validator_indices(state, epoch), system.cmp)
|
||||
|
||||
cache.beacon_proposer_indices[slot] =
|
||||
compute_proposer_index(state, indices, seed)
|
||||
cache.beacon_proposer_indices[slot]
|
||||
except KeyError:
|
||||
raiseAssert("Cached entries are added before use")
|
||||
return cache.beacon_proposer_indices.mgetOrPut(
|
||||
slot, compute_proposer_index(state, indices, seed))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_beacon_proposer_index
|
||||
func get_beacon_proposer_index*(state: BeaconState, cache: var StateCache):
|
||||
|
@ -300,33 +342,3 @@ func get_committee_assignment*(
|
|||
if validator_index in committee:
|
||||
return some((committee, idx, slot))
|
||||
none(tuple[a: seq[ValidatorIndex], b: CommitteeIndex, c: Slot])
|
||||
|
||||
func get_committee_assignments*(
|
||||
state: BeaconState, epoch: Epoch,
|
||||
validator_indices: HashSet[ValidatorIndex]):
|
||||
seq[tuple[subnetIndex: uint64, slot: Slot]] =
|
||||
let next_epoch = get_current_epoch(state) + 1
|
||||
doAssert epoch <= next_epoch
|
||||
|
||||
var cache = StateCache()
|
||||
let start_slot = compute_start_slot_at_epoch(epoch)
|
||||
|
||||
let committees_per_slot =
|
||||
get_committee_count_per_slot(state, epoch, cache)
|
||||
|
||||
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
|
||||
for index in 0'u64 ..< committees_per_slot:
|
||||
let idx = index.CommitteeIndex
|
||||
if not disjoint(validator_indices,
|
||||
get_beacon_committee(state, slot, idx, cache).toHashSet):
|
||||
result.add(
|
||||
(compute_subnet_for_attestation(committees_per_slot, slot, idx),
|
||||
slot))
|
||||
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#validator-assignments
|
||||
func is_proposer(
|
||||
state: BeaconState, validator_index: ValidatorIndex): bool {.used.} =
|
||||
var cache = StateCache()
|
||||
let proposer_index = get_beacon_proposer_index(state, cache)
|
||||
proposer_index.isSome and proposer_index.get == validator_index
|
||||
|
||||
|
|
|
@ -414,7 +414,7 @@ proc broadcastAggregatedAttestations(
|
|||
# TODO https://github.com/status-im/nim-beacon-chain/issues/545
|
||||
# this assumes in-process private keys
|
||||
validator.privKey,
|
||||
trailing_distance)
|
||||
trailing_distance, cache)
|
||||
|
||||
# Don't broadcast when, e.g., this node isn't an aggregator
|
||||
if aggregateAndProof.isSome:
|
||||
|
|
|
@ -188,12 +188,6 @@ template processEpochScenarioImpl(
|
|||
|
||||
when needCache:
|
||||
var cache = StateCache()
|
||||
let epoch = state.data.get_current_epoch()
|
||||
cache.shuffled_active_validator_indices[epoch] =
|
||||
get_shuffled_active_validator_indices(state.data, epoch)
|
||||
|
||||
# Epoch transitions can't fail (TODO is this true?)
|
||||
when needCache:
|
||||
transitionFn(state.data, cache)
|
||||
else:
|
||||
transitionFn(state.data)
|
||||
|
|
|
@ -3,7 +3,7 @@ import
|
|||
../tests/[testblockutil],
|
||||
../beacon_chain/[extras],
|
||||
../beacon_chain/ssz/[merkleization, ssz_serialization],
|
||||
../beacon_chain/spec/[beaconstate, datatypes, digest, helpers, presets]
|
||||
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers, presets]
|
||||
|
||||
template withTimer*(stats: var RunningStat, body: untyped) =
|
||||
# TODO unify timing somehow
|
||||
|
@ -99,3 +99,19 @@ proc printTimers*[Timers: enum](
|
|||
echo "Validators: ", state.validators.len, ", epoch length: ", SLOTS_PER_EPOCH
|
||||
echo "Validators per attestation (mean): ", attesters.mean
|
||||
printTimers(validate, timers)
|
||||
|
||||
proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) =
|
||||
## Combine the signature and participation bitfield, with the assumption that
|
||||
## the same data is being signed - if the signatures overlap, they are not
|
||||
## combined.
|
||||
|
||||
doAssert tgt.data == src.data
|
||||
|
||||
# In a BLS aggregate signature, one needs to count how many times a
|
||||
# particular public key has been added - since we use a single bit per key, we
|
||||
# can only it once, thus we can never combine signatures that overlap already!
|
||||
if not tgt.aggregation_bits.overlaps(src.aggregation_bits):
|
||||
tgt.aggregation_bits.combine(src.aggregation_bits)
|
||||
|
||||
if skipBlsValidation notin flags:
|
||||
tgt.signature.aggregate(src.signature)
|
||||
|
|
|
@ -35,8 +35,6 @@ proc addMockAttestations*(
|
|||
|
||||
# TODO: Working with an unsigned Gwei balance is a recipe for underflows to happen
|
||||
var cache = StateCache()
|
||||
cache.shuffled_active_validator_indices[epoch] =
|
||||
get_shuffled_active_validator_indices(state, epoch)
|
||||
var remaining_balance = state.get_total_active_balance(cache).int64 * 2 div 3
|
||||
|
||||
let
|
||||
|
|
|
@ -7,16 +7,12 @@
|
|||
|
||||
{.used.}
|
||||
|
||||
import
|
||||
../beacon_chain/spec/datatypes,
|
||||
../beacon_chain/ssz
|
||||
|
||||
import
|
||||
unittest,
|
||||
chronicles,
|
||||
stew/byteutils,
|
||||
./testutil, ./testblockutil,
|
||||
../beacon_chain/spec/[digest, validator, state_transition,
|
||||
./testutil, ./testblockutil, ../research/simutils,
|
||||
../beacon_chain/spec/[crypto, datatypes, digest, validator, state_transition,
|
||||
helpers, beaconstate, presets],
|
||||
../beacon_chain/[beacon_node_types, attestation_pool, block_pool, extras],
|
||||
../beacon_chain/fork_choice/[fork_choice_types, fork_choice]
|
||||
|
|
Loading…
Reference in New Issue