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:
commit
153b4206e8
6
setup.py
6
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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue