diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4666d7dbf..7baca73e6 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -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`. diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index cacfebe7d..b01e7a3af 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -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.