Merge pull request #520 from ethereum/next-epoch-shuffling

helpers and notes for shuffling lookahead
This commit is contained in:
Danny Ryan 2019-01-30 17:24:45 -08:00 committed by GitHub
commit e400c28372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 8 deletions

View File

@ -64,6 +64,7 @@
- [`get_shuffling`](#get_shuffling)
- [`get_previous_epoch_committee_count`](#get_previous_epoch_committee_count)
- [`get_current_epoch_committee_count`](#get_current_epoch_committee_count)
- [`get_next_epoch_committee_count`](#get_next_epoch_committee_count)
- [`get_crosslink_committees_at_slot`](#get_crosslink_committees_at_slot)
- [`get_block_root`](#get_block_root)
- [`get_randao_mix`](#get_randao_mix)
@ -72,6 +73,7 @@
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
- [`merkle_root`](#merkle_root)
- [`get_attestation_participants`](#get_attestation_participants)
- [`is_power_of_two`](#is_power_of_two)
- [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-)
- [`get_effective_balance`](#get_effective_balance)
- [`get_fork_version`](#get_fork_version)
@ -815,31 +817,61 @@ def get_current_epoch_committee_count(state: BeaconState) -> int:
return get_epoch_committee_count(len(current_active_validators))
```
### `get_next_epoch_committee_count`
```python
def get_next_epoch_committee_count(state: BeaconState) -> int:
next_active_validators = get_active_validator_indices(
state.validator_registry,
get_current_epoch(state) + 1,
)
return get_epoch_committee_count(len(next_active_validators))
```
### `get_crosslink_committees_at_slot`
```python
def get_crosslink_committees_at_slot(state: BeaconState,
slot: SlotNumber) -> List[Tuple[List[ValidatorIndex], ShardNumber]]:
slot: SlotNumber,
registry_change=False: bool) -> List[Tuple[List[ValidatorIndex], ShardNumber]]:
"""
Return the list of ``(committee, shard)`` tuples for the ``slot``.
Note: There are two possible shufflings for crosslink committees for a
``slot`` in the next epoch -- with and without a `registry_change`
"""
epoch = slot_to_epoch(slot)
current_epoch = get_current_epoch(state)
previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else current_epoch
next_epoch = current_epoch + 1
assert previous_epoch <= epoch < next_epoch
assert previous_epoch <= epoch <= next_epoch
if epoch < current_epoch:
if epoch == previous_epoch:
committees_per_epoch = get_previous_epoch_committee_count(state)
seed = state.previous_epoch_seed
shuffling_epoch = state.previous_calculation_epoch
shuffling_start_shard = state.previous_epoch_start_shard
else:
elif epoch == current_epoch:
committees_per_epoch = get_current_epoch_committee_count(state)
seed = state.current_epoch_seed
shuffling_epoch = state.current_calculation_epoch
shuffling_start_shard = state.current_epoch_start_shard
elif epoch == next_epoch:
current_committees_per_epoch = get_current_epoch_committee_count(state)
committees_per_epoch = get_next_epoch_committee_count(state)
shuffling_epoch = next_epoch
epochs_since_last_registry_update = current_epoch - state.validator_registry_update_epoch
if registry_change:
seed = generate_seed(state, next_epoch)
shuffling_start_shard = (state.current_epoch_start_shard + current_committees_per_epoch) % SHARD_COUNT
elif epochs_since_last_registry_update > 1 and is_power_of_two(epochs_since_last_registry_update):
seed = generate_seed(state, next_epoch)
shuffling_start_shard = state.current_epoch_start_shard
else:
seed = state.current_epoch_seed
shuffling_start_shard = state.current_epoch_start_shard
shuffling = get_shuffling(
seed,
@ -896,7 +928,7 @@ def get_active_index_root(state: BeaconState,
"""
Return the index root at a recent ``epoch``.
"""
assert get_current_epoch(state) - LATEST_INDEX_ROOTS_LENGTH < epoch <= get_current_epoch(state)
assert get_current_epoch(state) - LATEST_INDEX_ROOTS_LENGTH + ENTRY_EXIT_DELAY < epoch <= get_current_epoch(state) + ENTRY_EXIT_DELAY
return state.latest_index_roots[epoch % LATEST_INDEX_ROOTS_LENGTH]
```
@ -966,6 +998,19 @@ def get_attestation_participants(state: BeaconState,
return participants
```
### `is_power_of_two`
```
def is_power_of_two(value: int) -> bool:
"""
Check if ``value`` is a power of two integer.
"""
if value == 0:
return False
else:
return 2**int(math.log2(value)) == value
```
### `int_to_bytes1`, `int_to_bytes2`, ...
`int_to_bytes1(x): return x.to_bytes(1, 'big')`, `int_to_bytes2(x): return x.to_bytes(2, 'big')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32, 48, 96.
@ -1507,7 +1552,9 @@ def get_initial_beacon_state(initial_validator_deposits: List[Deposit],
if get_effective_balance(state, validator_index) >= MAX_DEPOSIT_AMOUNT:
activate_validator(state, validator_index, is_genesis=True)
state.latest_index_roots[GENESIS_EPOCH % LATEST_INDEX_ROOTS_LENGTH] = hash_tree_root(get_active_validator_indices(state.validator_registry, GENESIS_EPOCH))
genesis_active_index_root = hash_tree_root(get_active_validator_indices(state, GENESIS_EPOCH))
for index in range(LATEST_INDEX_ROOTS_LENGTH):
state.latest_index_roots[index] = genesis_active_index_root
state.current_epoch_seed = generate_seed(state, GENESIS_EPOCH)
return state
@ -1933,7 +1980,6 @@ First, update the following:
* Set `state.previous_calculation_epoch = state.current_calculation_epoch`.
* Set `state.previous_epoch_start_shard = state.current_epoch_start_shard`.
* Set `state.previous_epoch_seed = state.current_epoch_seed`.
* Set `state.latest_index_roots[next_epoch % LATEST_INDEX_ROOTS_LENGTH] = hash_tree_root(get_active_validator_indices(state, next_epoch))`.
If the following are satisfied:
@ -1996,7 +2042,7 @@ and perform the following updates:
If a validator registry update does _not_ happen do the following:
* Let `epochs_since_last_registry_update = current_epoch - state.validator_registry_update_epoch`.
* If `epochs_since_last_registry_update` is an exact power of 2:
* If `epochs_since_last_registry_update > 1` and `is_power_of_two(epochs_since_last_registry_update)`:
* Set `state.current_calculation_epoch = next_epoch`.
* Set `state.current_epoch_seed = generate_seed(state, state.current_calculation_epoch)`
* _Note_ that `state.current_epoch_start_shard` is left unchanged.
@ -2048,6 +2094,7 @@ def process_penalties_and_exits(state: BeaconState) -> None:
#### Final updates
* Set `state.latest_index_roots[(next_epoch + ENTRY_EXIT_DELAY) % LATEST_INDEX_ROOTS_LENGTH] = hash_tree_root(get_active_validator_indices(state, next_epoch + ENTRY_EXIT_DELAY))`.
* Set `state.latest_penalized_balances[(next_epoch) % LATEST_PENALIZED_EXIT_LENGTH] = state.latest_penalized_balances[current_epoch % LATEST_PENALIZED_EXIT_LENGTH]`.
* Set `state.latest_randao_mixes[next_epoch % LATEST_RANDAO_MIXES_LENGTH] = get_randao_mix(state, current_epoch)`.
* Remove any `attestation` in `state.latest_attestations` such that `slot_to_epoch(attestation.data.slot) < current_epoch`.

View File

@ -50,6 +50,7 @@ __NOTICE__: This document is a work-in-progress for researchers and implementers
- [Aggregation bitfield](#aggregation-bitfield)
- [Custody bitfield](#custody-bitfield)
- [Aggregate signature](#aggregate-signature)
- [Responsibility lookahead](#responsibility-lookahead)
- [How to avoid slashing](#how-to-avoid-slashing)
- [Proposer slashing](#proposer-slashing)
- [Attester slashing](#attester-slashing)
@ -329,6 +330,39 @@ signed_attestation_data = bls_sign(
)
```
## Responsibility lookahead
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming responsibilities of proposing and attesting dictated by the shuffling and slot.
There are three possibilities for the shuffling at the next epoch:
1. The shuffling changes due to a "validator registry change".
2. The shuffling changes due to `epochs_since_last_registry_update` being an exact power of 2 greater than 1.
3. The shuffling remains the same (i.e. the validator is in the same shard committee).
Either (2) or (3) occurs if (1) fails. The choice between (2) and (3) is deterministic based upon `epochs_since_last_registry_update`.
`get_crosslink_committees_at_slot` is designed to be able to query slots in the next epoch. When querying slots in the next epoch there are two options -- with and without a `registry_change` -- which is the optional third parameter of the function. The following helper can be used to get the potential crosslink committees in the next epoch for a given `validator_index`. This function returns a list of 2 shard committee tuples.
```python
def get_next_epoch_crosslink_committees(state: BeaconState,
validator_index: ValidatorIndex) -> List[Tuple[ValidatorIndex], ShardNumber]:
current_epoch = get_current_epoch(state)
next_epoch = current_epoch + 1
next_epoch_start_slot = get_epoch_start_slot(next_epoch)
potential_committees = []
for validator_registry in [False, True]:
for slot in range(next_epoch_start_slot, next_epoch_start_slot + EPOCH_LENGTH):
shard_committees = get_crosslink_committees_at_slot(state, slot, validator_registry)
selected_committees = [committee for committee in shard_committees if validator_index in committee[0]]
if len(selected_committees) > 0:
potential_assignments.append(selected_committees)
break
return potential_assignments
```
`get_next_epoch_crosslink_committees` should be called at the beginning of each epoch to plan for the next epoch. A validator should always plan for both values of `registry_change` as a possibility unless the validator can concretely eliminate one of the options. Planning for a future shuffling involves noting at which slot one might have to attest and propose and also which shard one should begin syncing (in phase 1+).
## How to avoid slashing
"Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed -- [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed.