Merge pull request #1904 from ethereum/epochwise_committee_count_per_slot

Avoid state in p2p validation, compute committee count per slot for epoch
This commit is contained in:
Danny Ryan 2020-06-18 09:52:40 -06:00 committed by GitHub
commit 153b4206e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 59 additions and 42 deletions

View File

@ -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(

View File

@ -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

View File

@ -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 pre-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.

View File

@ -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
```

View File

@ -593,7 +593,10 @@ 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`
@ -748,7 +751,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)
@ -928,7 +931,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 = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot)

View File

@ -151,7 +151,7 @@ def get_shard_winning_roots(state: BeaconState,
winning_roots = []
online_indices = get_online_validator_indices(state)
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)):
shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot)
# All attestations in the block for this committee/shard and are "on time"

View File

@ -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)

View File

@ -259,7 +259,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:
@ -277,7 +277,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)
@ -306,7 +306,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

View File

@ -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:

View File

@ -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]

View File

@ -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)

View File

@ -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
)

View File

@ -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