diff --git a/setup.py b/setup.py index 6bee87080..cc3b00da9 100644 --- a/setup.py +++ b/setup.py @@ -189,10 +189,10 @@ get_base_reward = cache_this( lambda state, index: (state.validators.hash_tree_root(), state.slot, index), _get_base_reward, lru_size=2048) -_get_committee_count_at_slot = get_committee_count_at_slot -get_committee_count_at_slot = cache_this( +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_at_slot, lru_size=SLOTS_PER_EPOCH * 3) + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) _get_active_validator_indices = get_active_validator_indices get_active_validator_indices = cache_this( diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 7914dc458..6533dcb7f 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -89,7 +89,7 @@ - [`get_active_validator_indices`](#get_active_validator_indices) - [`get_validator_churn_limit`](#get_validator_churn_limit) - [`get_seed`](#get_seed) - - [`get_committee_count_at_slot`](#get_committee_count_at_slot) + - [`get_committee_count_per_slot`](#get_committee_count_per_slot) - [`get_beacon_committee`](#get_beacon_committee) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`get_total_balance`](#get_total_balance) @@ -948,14 +948,13 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes return hash(domain_type + int_to_bytes(epoch, length=8) + mix) ``` -#### `get_committee_count_at_slot` +#### `get_committee_count_per_slot` ```python -def get_committee_count_at_slot(state: BeaconState, slot: Slot) -> uint64: +def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: """ - Return the number of committees at ``slot``. + Return the number of committees in each ``slot`` for the given epoch. """ - epoch = compute_epoch_at_slot(slot) return max(1, min( MAX_COMMITTEES_PER_SLOT, len(get_active_validator_indices(state, epoch)) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, @@ -970,7 +969,7 @@ def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) Return the beacon committee at ``slot`` for ``index``. """ epoch = compute_epoch_at_slot(slot) - committees_per_slot = get_committee_count_at_slot(state, slot) + committees_per_slot = get_committee_count_per_slot(state, epoch) return compute_committee( indices=get_active_validator_indices(state, epoch), seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), @@ -1720,7 +1719,7 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data - assert data.index < get_committee_count_at_slot(state, data.slot) + assert data.index < get_committee_count_per_slot(state, data.target.epoch) assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) assert data.target.epoch == compute_epoch_at_slot(data.slot) assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 6c4cdd2a6..211508f00 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -275,18 +275,22 @@ Additional global topics are used to propagate lower frequency validator message Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are: - `beacon_attestation_{subnet_id}` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet. - - _[REJECT]_ The attestation is for the correct subnet (i.e. `compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index) == subnet_id`). + - _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be computed along with the committee information for the signature check. - _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). - - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1`). + - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. - _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. - _[REJECT]_ The signature of `attestation` is valid. #### Attestations and Aggregation -Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. `beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1. +Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. +The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. +`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1. +The subnets are rotated through with `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` subnets per slot. + +Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)}` as `Attestation`s. -Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index)}` as `Attestation`s. Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `AggregateAndProof`s. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 35c52f346..e325f75f6 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -172,8 +172,9 @@ def get_committee_assignment(state: BeaconState, assert epoch <= next_epoch start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - for index in range(get_committee_count_at_slot(state, Slot(slot))): + for index in range(committee_count_per_slot): committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) if validator_index in committee: return committee, CommitteeIndex(index), Slot(slot) @@ -199,8 +200,10 @@ The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahe Specifically a validator should: * Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments. -* Find peers of the pubsub topic `beacon_attestation_{compute_subnet_for_attestation(state, slot, committee_index)}`. - * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][compute_subnet_for_attestation(state, slot, committee_index)] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. +* Calculate the committees per slot for the next epoch: `committees_per_slot = get_committee_count_per_slot(state, next_epoch)` +* Calculate the subnet index: `subnet_id = compute_subnet_for_attestation(committees_per_slot, slot, committee_index)` +* Find peers of the pubsub topic `beacon_attestation_{subnet_id}`. + * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][subnet_id] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. * If the validator is assigned to be an aggregator for the slot (see `is_aggregator()`), then subscribe to the topic. *Note*: If the validator is _not_ assigned to be an aggregator, the validator only needs sufficient number of peers on the topic to be able to publish messages. The validator does not need to _subscribe_ and listen to all messages on the topic. @@ -425,16 +428,20 @@ def get_attestation_signature(state: BeaconState, attestation_data: AttestationD #### Broadcast attestation -Finally, the validator broadcasts `attestation` to the associated attestation subnet -- the `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.committee_index)}` pubsub topic. +Finally, the validator broadcasts `attestation` to the associated attestation subnet, the `beacon_attestation_{subnet_id}` pubsub topic. + +The `subnet_id` for the `attestation` is calculated with: +- Let `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`. +- Let `subnet_id = compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.committee_index)`. ```python -def compute_subnet_for_attestation(state: BeaconState, slot: Slot, committee_index: CommitteeIndex) -> uint64: +def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: """ Compute the correct subnet for an attestation for Phase 0. Note, this mimics expected Phase 1 behavior where attestations will be mapped to their shard subnet. """ slots_since_epoch_start = slot % SLOTS_PER_EPOCH - committees_since_epoch_start = get_committee_count_at_slot(state, slot) * slots_since_epoch_start + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start return (committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT ``` diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4e4778abe..472182a45 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -592,7 +592,8 @@ def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: S """ Return the sum of committee counts in range ``[start_slot, stop_slot)``. """ - return sum(get_committee_count_at_slot(state, Slot(slot)) for slot in range(start_slot, stop_slot)) + return sum(get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(slot))) + for slot in range(start_slot, stop_slot)) ``` #### `get_start_shard` @@ -757,7 +758,7 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: ```python def validate_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data - assert data.index < get_committee_count_at_slot(state, data.slot) + assert data.index < get_committee_count_per_slot(state, data.target.epoch) assert data.index < get_active_shard_count(state) assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) assert data.target.epoch == compute_epoch_at_slot(data.slot) @@ -935,7 +936,7 @@ def process_crosslinks(state: BeaconState, shard_transitions: Sequence[ShardTransition], attestations: Sequence[Attestation]) -> None: on_time_attestation_slot = compute_previous_slot(state.slot) - committee_count = get_committee_count_at_slot(state, on_time_attestation_slot) + committee_count = get_committee_count_per_slot(state, compute_epoch_at_slot(on_time_attestation_slot)) for committee_index in map(CommitteeIndex, range(committee_count)): # All attestations in the block for this committee/shard and current slot shard_attestations = [ diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 558a1b3bf..66445abb8 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -148,7 +148,7 @@ def get_shard_winning_roots(state: BeaconState, shards = [] winning_roots = [] online_indices = get_online_validator_indices(state) - committee_count = get_committee_count_at_slot(state, state.slot) + committee_count = get_committee_count_per_slot(state, get_current_epoch(state)) for committee_index in map(CommitteeIndex, range(committee_count)): shard = compute_shard_from_committee_index(state, committee_index, state.slot) # All attestations in the block for this committee/shard and are "on time" diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py index f9a739804..859fc797f 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py @@ -154,7 +154,7 @@ def test_filtered_block_tree(spec, state): attestations = [] for i in range(spec.SLOTS_PER_EPOCH): slot = rogue_block.slot + i - for index in range(spec.get_committee_count_at_slot(non_viable_state, slot)): + for index in range(spec.get_committee_count_per_slot(non_viable_state, spec.compute_epoch_at_slot(slot))): attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True) attestations.append(attestation) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 23eba34da..5016ac5df 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -257,7 +257,7 @@ def next_epoch_with_attestations(spec, block = build_empty_block_for_next_slot(spec, post_state) if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 - committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) + committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): for index in range(committees_per_slot): if spec.fork == PHASE1: @@ -275,7 +275,7 @@ def next_epoch_with_attestations(spec, if fill_prev_epoch: slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 - committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) + committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) for index in range(committees_per_slot): prev_attestation = get_valid_attestation( spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) @@ -304,7 +304,7 @@ def prepare_state_with_attestations(spec, state, participation_fn=None): for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): # create an attestation for each index in each slot in epoch if state.slot < next_epoch_start_slot: - for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)): + for committee_index in range(spec.get_committee_count_per_slot(state, spec.get_current_epoch(state))): def temp_participants_filter(comm): if participation_fn is None: return comm diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index ddf66f6c2..6957f2283 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -75,7 +75,7 @@ def get_shard_transitions(spec, parent_beacon_state, shard_blocks): def get_committee_index_of_shard(spec, state, slot, shard): # Optional[CommitteeIndex] active_shard_count = spec.get_active_shard_count(state) - committee_count = spec.get_committee_count_at_slot(state, slot) + committee_count = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot)) start_shard = spec.get_start_shard(state, slot) for committee_index in range(committee_count): if (start_shard + committee_index) % active_shard_count == shard: diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py index 5de8913d6..28fd9ac9e 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py @@ -139,7 +139,7 @@ def test_wrong_index_for_committee_signature(spec, state): @spec_state_test @never_bls def test_wrong_index_for_slot(spec, state): - while spec.get_committee_count_at_slot(state, state.slot) >= spec.MAX_COMMITTEES_PER_SLOT: + while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT: state.validators = state.validators[:len(state.validators) // 2] state.balances = state.balances[:len(state.balances) // 2] diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py index 09af2126d..73091a1c7 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py @@ -27,8 +27,8 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support remaining_balance = int(total_balance * 2 // 3) # can become negative start_slot = spec.compute_start_slot_at_epoch(epoch) + committees_per_slot = spec.get_committee_count_per_slot(state, epoch) for slot in range(start_slot, start_slot + spec.SLOTS_PER_EPOCH): - committees_per_slot = spec.get_committee_count_at_slot(state, slot) for index in range(committees_per_slot): # Check if we already have had sufficient balance. (and undone if we don't want it). # If so, do not create more attestations. (we do not have empty pending attestations normally anyway) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py index 27afd4a4e..c443545b9 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py @@ -10,11 +10,16 @@ from eth2spec.test.helpers.state import next_epoch @spec_state_test def test_get_committee_count_delta(spec, state): assert spec.get_committee_count_delta(state, 0, 0) == 0 - assert spec.get_committee_count_at_slot(state, 0) != 0 - assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_at_slot(state, 0) - assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_at_slot(state, 1) - assert spec.get_committee_count_delta(state, 0, 2) == ( - spec.get_committee_count_at_slot(state, 0) + spec.get_committee_count_at_slot(state, 1) + assert spec.get_committee_count_per_slot(state, 0) != 0 + assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_per_slot(state, 0) + assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_per_slot(state, 0) + assert spec.get_committee_count_delta(state, 0, 2) == spec.get_committee_count_per_slot(state, 0) * 2 + assert spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) == ( + spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH + ) + assert spec.get_committee_count_delta(state, 0, 2 * spec.SLOTS_PER_EPOCH) == ( + spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH + + spec.get_committee_count_per_slot(state, 1) * spec.SLOTS_PER_EPOCH ) diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index e749b1e3d..84c4b9fe5 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -26,7 +26,7 @@ def run_get_committee_assignment(spec, state, epoch, validator_index, valid=True committee, committee_index, slot = assignment assert spec.compute_epoch_at_slot(slot) == epoch assert committee == spec.get_beacon_committee(state, slot, committee_index) - assert committee_index < spec.get_committee_count_at_slot(state, slot) + assert committee_index < spec.get_committee_count_per_slot(state, epoch) assert validator_index in committee assert valid except AssertionError: @@ -359,13 +359,12 @@ def test_get_attestation_signature_phase0(spec, state): def test_compute_subnet_for_attestation(spec, state): for committee_idx in range(spec.MAX_COMMITTEES_PER_SLOT): for slot in range(state.slot, state.slot + spec.SLOTS_PER_EPOCH): - actual_subnet_id = spec.compute_subnet_for_attestation(state, slot, committee_idx) + committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot)) + actual_subnet_id = spec.compute_subnet_for_attestation(committees_per_slot, slot, committee_idx) slots_since_epoch_start = slot % spec.SLOTS_PER_EPOCH - committees_since_epoch_start = spec.get_committee_count_at_slot( - state, slot) * slots_since_epoch_start - expected_subnet_id = (committees_since_epoch_start + - committee_idx) % spec.ATTESTATION_SUBNET_COUNT + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start + expected_subnet_id = (committees_since_epoch_start + committee_idx) % spec.ATTESTATION_SUBNET_COUNT assert actual_subnet_id == expected_subnet_id