From 6f56c379d6cca55c9181eb825b2c4dccacd89fca Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 17 Apr 2019 14:06:28 +1000 Subject: [PATCH 01/26] Simplify get_justification_and_finalization_deltas Cosmetic changes related to `get_justification_and_finalization_deltas`: * Review naming of misc helper functions and variables * Abstract away common logic and rework for readability * Add `MAX_FINALITY_LOOKBACK` and `BASE_REWARDS_PER_EPOCH` constants * Rescale `INACTIVITY_PENALTY_QUOTIENT` Substantive changes: * Make logic relative to `previous_epoch` throughout (as opposed to mixing `current_epoch` and `previous_epoch`) * Replace inclusion delay bonus by an inclusion delay penalty --- specs/core/0_beacon-chain.md | 144 +++++++++++++++-------------------- 1 file changed, 63 insertions(+), 81 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ddb0eb6db..706f9154f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -185,6 +185,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) | | `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | | `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | +| `BASE_REWARDS_PER_EPOCH` | `5` | | `SHUFFLE_ROUND_COUNT` | 90 | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) @@ -234,6 +235,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | | `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours | +| `MAX_FINALITY_LOOKBACK` | `2**2` (= 4) | epochs | 25.6 minutes | * `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH` @@ -252,7 +254,7 @@ These configurations are updated for releases, but may be out of sync during `de | `BASE_REWARD_QUOTIENT` | `2**5` (= 32) | | `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) | | `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) | -| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) | +| `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) | | `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) | * The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch. @@ -875,7 +877,7 @@ def compute_committee(validator_indices: List[ValidatorIndex], ] ``` -**Note**: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. +Note: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. ### `get_crosslink_committees_at_slot` @@ -1582,7 +1584,7 @@ Transition section notes: Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid. -_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. +Note: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. ### State caching @@ -1612,30 +1614,20 @@ The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SL We define epoch transition helper functions: ```python -def get_current_total_balance(state: BeaconState) -> Gwei: - return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state))) -``` - -```python -def get_previous_total_balance(state: BeaconState) -> Gwei: +def get_previous_epoch_total_balance(state: BeaconState) -> Gwei: return get_total_balance(state, get_active_validator_indices(state, get_previous_epoch(state))) ``` -```python -def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]: - output = set() - for a in attestations: - output = output.union(get_attestation_participants(state, a.data, a.aggregation_bitfield)) - return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output))) -``` +Note: The balance computed by `get_previous_epoch_total_balance` may be different to the actual total balance during the previous epoch transition. Due to the bounds on per-epoch validator churn and per-epoch rewards/penalties, the maximum balance difference is low and only marginally affects consensus safety. ```python -def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation]) -> Gwei: - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) +def get_current_epoch_total_balance(state: BeaconState) -> Gwei: + return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state))) ``` + ```python -def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]: +def get_current_epoch_matching_target_attestations(state: BeaconState) -> List[PendingAttestation]: return [ a for a in state.current_epoch_attestations if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state))) @@ -1643,7 +1635,7 @@ def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingA ``` ```python -def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]: +def get_previous_epoch_matching_target_attestations(state: BeaconState) -> List[PendingAttestation]: return [ a for a in state.previous_epoch_attestations if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) @@ -1658,7 +1650,18 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe ] ``` -**Note**: Total balances computed for the previous epoch might be marginally different than the actual total balances during the previous epoch transition. Due to the tight bound on validator churn each epoch and small per-epoch rewards/penalties, the potential balance difference is very low and only marginally affects consensus safety. +```python +def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]: + output = set() + for a in attestations: + output = output.union(get_attestation_participants(state, a.data, a.aggregation_bitfield)) + return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output))) +``` + +```python +def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation]) -> Gwei: + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) +``` ```python def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]: @@ -1701,13 +1704,13 @@ def update_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - previous_boundary_attesting_balance = get_attesting_balance(state, get_previous_epoch_boundary_attestations(state)) - if previous_boundary_attesting_balance * 3 >= get_previous_total_balance(state) * 2: + previous_epoch_matching_target_balance = get_attesting_balance(state, get_previous_epoch_matching_target_attestations(state)) + if previous_epoch_matching_target_balance * 3 >= get_previous_epoch_total_balance(state) * 2: state.current_justified_epoch = get_previous_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 1) - current_boundary_attesting_balance = get_attesting_balance(state, get_current_epoch_boundary_attestations(state)) - if current_boundary_attesting_balance * 3 >= get_current_total_balance(state) * 2: + current_epoch_matching_target_balance = get_attesting_balance(state, get_current_epoch_matching_target_attestations(state)) + if current_epoch_matching_target_balance * 3 >= get_current_epoch_total_balance(state) * 2: state.current_justified_epoch = get_current_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 0) @@ -1771,80 +1774,58 @@ def maybe_reset_eth1_period(state: BeaconState) -> None: #### Rewards and penalties -First, we define some additional helpers: +We first define a helper: ```python -def get_base_reward_from_total_balance(state: BeaconState, total_balance: Gwei, index: ValidatorIndex) -> Gwei: +def get_base_reward(state: BeaconState, total_balance: Gwei, index: ValidatorIndex) -> Gwei: if total_balance == 0: return 0 adjusted_quotient = integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT - return get_effective_balance(state, index) // adjusted_quotient // 5 + return get_effective_balance(state, index) // adjusted_quotient // BASE_REWARDS_PER_EPOCH ``` -```python -def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: - return get_base_reward_from_total_balance(state, get_previous_total_balance(state), index) -``` - -```python -def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_since_finality: int) -> Gwei: - if epochs_since_finality <= 4: - extra_penalty = 0 - else: - extra_penalty = get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 - return get_base_reward(state, index) + extra_penalty -``` - -Note: When applying penalties in the following balance recalculations, implementers should make sure the `uint64` does not underflow. - ##### Justification and finalization ```python def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - current_epoch = get_current_epoch(state) - epochs_since_finality = current_epoch + 1 - state.finalized_epoch - rewards = [0 for index in range(len(state.validator_registry))] - penalties = [0 for index in range(len(state.validator_registry))] - # Some helper variables - boundary_attestations = get_previous_epoch_boundary_attestations(state) - boundary_attesting_balance = get_attesting_balance(state, boundary_attestations) - total_balance = get_previous_total_balance(state) - total_attesting_balance = get_attesting_balance(state, state.previous_epoch_attestations) - matching_head_attestations = get_previous_epoch_matching_head_attestations(state) - matching_head_balance = get_attesting_balance(state, matching_head_attestations) + previous_epoch = get_previous_epoch(state) eligible_validators = [ index for index, validator in enumerate(state.validator_registry) if ( - is_active_validator(validator, current_epoch) or - (validator.slashed and current_epoch < validator.withdrawable_epoch) + is_active_validator(validator, previous_epoch) or + (validator.slashed and previous_epoch < validator.withdrawable_epoch) ) ] - # Process rewards or penalties for all validators + rewards = [0 for index in range(len(state.validator_registry))] + penalties = [0 for index in range(len(state.validator_registry))] for index in eligible_validators: - base_reward = get_base_reward(state, index) - # Expected FFG source + base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index) + + # Micro-incentives for matching FFG source, matching FFG target, and matching head + for attestations in ( + state.previous_epoch_attestations, # Matching FFG source + get_previous_epoch_matching_target_attestations(state), # Matching FFG target + get_previous_epoch_matching_head_attestations(state), # Matching head + ): + if index in get_unslashed_attesting_indices(state, attestations): + rewards[index] += base_reward * get_attesting_balance(state, attestations) // get_previous_epoch_total_balance(state) + else: + penalties[index] += base_reward + + # Inclusion delay micro-penalty if index in get_unslashed_attesting_indices(state, state.previous_epoch_attestations): - rewards[index] += base_reward * total_attesting_balance // total_balance - # Inclusion speed bonus earliest_attestation = get_earliest_attestation(state, state.previous_epoch_attestations, index) inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - rewards[index] += base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay - else: - penalties[index] += base_reward - # Expected FFG target - if index in get_unslashed_attesting_indices(state, boundary_attestations): - rewards[index] += base_reward * boundary_attesting_balance // total_balance - else: - penalties[index] += get_inactivity_penalty(state, index, epochs_since_finality) - # Expected head - if index in get_unslashed_attesting_indices(state, matching_head_attestations): - rewards[index] += base_reward * matching_head_balance // total_balance - else: - penalties[index] += base_reward - # Take away max rewards if we're not finalizing - if epochs_since_finality > 4: - penalties[index] += base_reward * 4 + penalties[index] += base_reward * (1 - MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay) + + # Inactivity penalty + epochs_since_finality = previous_epoch + 1 - state.finalized_epoch + if epochs_since_finality > MAX_FINALITY_LOOKBACK: + penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward + if index not in get_unslashed_attesting_indices(state, get_previous_epoch_matching_target_attestations(state)): + penalties[index] += get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT + return [rewards, penalties] ``` @@ -1862,10 +1843,11 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: participating_balance = get_total_balance(state, participants) total_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: + base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index) if index in participants: - rewards[index] += get_base_reward(state, index) * participating_balance // total_balance + rewards[index] += base_reward * participating_balance // total_balance else: - penalties[index] += get_base_reward(state, index) + penalties[index] += base_reward return [rewards, penalties] ``` @@ -2176,7 +2158,7 @@ def process_proposer_attestation_rewards(state: BeaconState) -> None: for pending_attestations in (state.previous_epoch_attestations, state.current_epoch_attestations): for index in get_unslashed_attesting_indices(state, pending_attestations): if get_earliest_attestation(state, pending_attestations, index).inclusion_slot == state.slot: - base_reward = get_base_reward_from_total_balance(state, get_current_total_balance(state), index) + base_reward = get_base_reward(state, get_current_epoch_total_balance(state), index) increase_balance(state, proposer_index, base_reward // PROPOSER_REWARD_QUOTIENT) ``` From cc92ee9f67877e4c286c0e0df12dab26b0356717 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 17 Apr 2019 15:53:24 +1000 Subject: [PATCH 02/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 706f9154f..d7f7095e4 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -185,7 +185,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) | | `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | | `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | -| `BASE_REWARDS_PER_EPOCH` | `5` | +| `BASE_REWARDS_PER_EPOCH` | `4` | | `SHUFFLE_ROUND_COUNT` | 90 | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) From 0f8b1c5f3a768989853f3a8928745ca25ac11f00 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 18 Apr 2019 10:56:15 +1000 Subject: [PATCH 03/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d7f7095e4..05dca84eb 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1817,11 +1817,11 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[ if index in get_unslashed_attesting_indices(state, state.previous_epoch_attestations): earliest_attestation = get_earliest_attestation(state, state.previous_epoch_attestations, index) inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - penalties[index] += base_reward * (1 - MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay) + penalties[index] += base_reward * (inclusion_delay - MIN_ATTESTATION_INCLUSION_DELAY) // (SLOTS_PER_EPOCH - MIN_ATTESTATION_INCLUSION_DELAY) # Inactivity penalty epochs_since_finality = previous_epoch + 1 - state.finalized_epoch - if epochs_since_finality > MAX_FINALITY_LOOKBACK: + if epochs_since_finality >= MIN_EPOCHS_TO_LEAK: penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward if index not in get_unslashed_attesting_indices(state, get_previous_epoch_matching_target_attestations(state)): penalties[index] += get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT From 3f9a65f1c8942c5c41dfc3ba00952465bafc6797 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 18 Apr 2019 10:59:15 +1000 Subject: [PATCH 04/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 05dca84eb..482e4da49 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -235,7 +235,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | | `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours | -| `MAX_FINALITY_LOOKBACK` | `2**2` (= 4) | epochs | 25.6 minutes | +| `MIN_EPOCHS_TO_LEAK` | `2**2` (= 4) | epochs | 25.6 minutes | * `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH` @@ -1820,8 +1820,8 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[ penalties[index] += base_reward * (inclusion_delay - MIN_ATTESTATION_INCLUSION_DELAY) // (SLOTS_PER_EPOCH - MIN_ATTESTATION_INCLUSION_DELAY) # Inactivity penalty - epochs_since_finality = previous_epoch + 1 - state.finalized_epoch - if epochs_since_finality >= MIN_EPOCHS_TO_LEAK: + epochs_since_finality = previous_epoch - state.finalized_epoch + if epochs_since_finality > MIN_EPOCHS_TO_LEAK: penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward if index not in get_unslashed_attesting_indices(state, get_previous_epoch_matching_target_attestations(state)): penalties[index] += get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT From 91921d8e86cb74de763be1079806d740a0b8a8a1 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 18 Apr 2019 11:52:14 +1000 Subject: [PATCH 05/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 482e4da49..864411c9f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -235,7 +235,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | | `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours | -| `MIN_EPOCHS_TO_LEAK` | `2**2` (= 4) | epochs | 25.6 minutes | +| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes | * `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH` @@ -1821,7 +1821,7 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[ # Inactivity penalty epochs_since_finality = previous_epoch - state.finalized_epoch - if epochs_since_finality > MIN_EPOCHS_TO_LEAK: + if epochs_since_finality > MIN_EPOCHS_TO_INACTIVITY_PENALTY: penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward if index not in get_unslashed_attesting_indices(state, get_previous_epoch_matching_target_attestations(state)): penalties[index] += get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT From f908c8d3e085432e8ec1dee8858cb9acff6eccaf Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 15:17:33 +1000 Subject: [PATCH 06/26] Revamped balances and incentivisation --- specs/core/0_beacon-chain.md | 224 ++++++++---------- specs/light_client/sync_protocol.md | 4 +- .../eth2spec/phase0/state_transition.py | 2 - .../test_process_attester_slashing.py | 2 +- .../block_processing/test_process_deposit.py | 2 +- .../test_process_proposer_slashing.py | 2 +- test_libs/pyspec/tests/helpers.py | 2 + test_libs/pyspec/tests/test_sanity.py | 8 +- 8 files changed, 106 insertions(+), 140 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 184811b52..84e9e4230 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -59,8 +59,6 @@ - [`is_active_validator`](#is_active_validator) - [`is_slashable_validator`](#is_slashable_validator) - [`get_active_validator_indices`](#get_active_validator_indices) - - [`get_balance`](#get_balance) - - [`set_balance`](#set_balance) - [`increase_balance`](#increase_balance) - [`decrease_balance`](#decrease_balance) - [`get_permuted_index`](#get_permuted_index) @@ -178,7 +176,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MAX_INDICES_PER_ATTESTATION` | `2**12` (= 4,096) | | `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | | `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | -| `BASE_REWARDS_PER_EPOCH` | `4` | +| `BASE_REWARDS_PER_EPOCH` | `5` | | `SHUFFLE_ROUND_COUNT` | 90 | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) @@ -410,7 +408,7 @@ The types are defined topologically to aid in facilitating an executable version # Was the validator slashed 'slashed': 'bool', # Rounded balance - 'high_balance': 'uint64' + 'effective_balance': 'uint64', } ``` @@ -733,29 +731,19 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> List[Valid return [i for i, v in enumerate(state.validator_registry) if is_active_validator(v, epoch)] ``` -### `get_balance` +### `get_next_epoch_effective_balance` ```python -def get_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: +def get_next_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> None: """ - Return the balance for a validator with the given ``index``. - """ - return state.balances[index] -``` - -### `set_balance` - -```python -def set_balance(state: BeaconState, index: ValidatorIndex, balance: Gwei) -> None: - """ - Set the balance for a validator with the given ``index`` in both ``BeaconState`` - and validator's rounded balance ``high_balance``. + Get validator effective balance for the next epoch """ + balance = min(state.balances[index], MAX_DEPOSIT_AMOUNT) validator = state.validator_registry[index] HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 - if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance: - validator.high_balance = balance - balance % HIGH_BALANCE_INCREMENT - state.balances[index] = balance + if validator.effective_balance > balance or validator.effective_balance + 3 * HALF_INCREMENT < balance: + return balance - balance % HIGH_BALANCE_INCREMENT + return validator.effective_balance ``` ### `increase_balance` @@ -763,9 +751,9 @@ def set_balance(state: BeaconState, index: ValidatorIndex, balance: Gwei) -> Non ```python def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: """ - Increase the balance for a validator with the given ``index`` by ``delta``. + Increase validator balance by ``delta``. """ - set_balance(state, index, get_balance(state, index) + delta) + state.balances[index] += delta ``` ### `decrease_balance` @@ -773,11 +761,9 @@ def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> ```python def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: """ - Decrease the balance for a validator with the given ``index`` by ``delta``. - Set to ``0`` when underflow. + Decrease validator balance by ``delta`` with underflow protection. """ - current_balance = get_balance(state, index) - set_balance(state, index, current_balance - delta if current_balance >= delta else 0) + state.balances[index] = state.balances[index] - delta if state.balances[index] >= delta else 0 ``` ### `get_permuted_index` @@ -969,18 +955,17 @@ def generate_seed(state: BeaconState, ### `get_beacon_proposer_index` ```python -def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: +def get_beacon_proposer_index(state: BeaconState, slot: Slot=None) -> ValidatorIndex: """ - Return the beacon proposer index at ``state.slot``. + Return the beacon proposer index at ``slot``. """ current_epoch = get_current_epoch(state) - - first_committee, _ = get_crosslink_committees_at_slot(state, state.slot)[0] + first_committee, _ = get_crosslink_committees_at_slot(state, slot if slot != None else state.slot)[0] i = 0 while True: candidate = first_committee[(current_epoch + i) % len(first_committee)] random_byte = hash(generate_seed(state, current_epoch) + int_to_bytes8(i // 32))[i % 32] - if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * random_byte: + if get_effective_balance(state, candidate, current_epoch) * 256 > MAX_DEPOSIT_AMOUNT * random_byte: return candidate i += 1 ``` @@ -1031,21 +1016,21 @@ def bytes_to_int(data: bytes) -> int: ### `get_effective_balance` ```python -def get_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: +def get_effective_balance(state: BeaconState, index: ValidatorIndex, epoch: Epoch) -> Gwei: """ Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. """ - return min(get_balance(state, index), MAX_DEPOSIT_AMOUNT) + return state.validator_registry[index].effective_balance if epoch == get_current_epoch(state) else get_next_epoch_effective_balance(state, index) ``` ### `get_total_balance` ```python -def get_total_balance(state: BeaconState, validators: List[ValidatorIndex]) -> Gwei: +def get_total_balance(state: BeaconState, validators: List[ValidatorIndex], epoch: Epoch) -> Gwei: """ Return the combined effective balance of an array of ``validators``. """ - return sum([get_effective_balance(state, i) for i in validators]) + return sum([get_effective_balance(state, i, epoch) for i in validators]) ``` ### `get_fork_version` @@ -1290,11 +1275,12 @@ def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistlebl Slash the validator with index ``slashed_index``. Note that this function mutates ``state``. """ + current_epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) state.validator_registry[slashed_index].slashed = True - state.validator_registry[slashed_index].withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH - slashed_balance = get_effective_balance(state, slashed_index) - state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance + state.validator_registry[slashed_index].withdrawable_epoch = current_epoch + LATEST_SLASHED_EXIT_LENGTH + slashed_balance = get_effective_balance(state, slashed_index, current_epoch) + state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance proposer_index = get_beacon_proposer_index(state) if whistleblower_index is None: @@ -1447,8 +1433,8 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], process_deposit(state, deposit) # Process genesis activations - for index in range(len(state.validator_registry)): - if get_effective_balance(state, index) >= MAX_DEPOSIT_AMOUNT: + for index, validator in enumerate(state.validator_registry): + if validator.effective_balance >= MAX_DEPOSIT_AMOUNT: activate_validator(state, index) genesis_active_index_root = hash_tree_root(get_active_validator_indices(state, GENESIS_EPOCH)) @@ -1520,7 +1506,7 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) # made for optimized implementations that precompute and save data def get_vote_count(block: BeaconBlock) -> int: return sum( - start_state.validator_registry[validator_index].high_balance + start_state.validator_registry[validator_index].effective_balance for validator_index, target in attestation_targets if get_ancestor(store, target, block.slot) == block ) @@ -1581,38 +1567,27 @@ The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SL We define epoch transition helper functions: ```python -def get_previous_epoch_total_balance(state: BeaconState) -> Gwei: - return get_total_balance(state, get_active_validator_indices(state, get_previous_epoch(state))) +def get_total_active_balance(state: BeaconState, epoch: Epoch) -> Gwei: + return get_total_balance(state, get_active_validator_indices(state, epoch), epoch) ``` -Note: The balance computed by `get_previous_epoch_total_balance` may be different to the actual total balance during the previous epoch transition. Due to the bounds on per-epoch validator churn and per-epoch rewards/penalties, the maximum balance difference is low and only marginally affects consensus safety. - ```python -def get_current_epoch_total_balance(state: BeaconState) -> Gwei: - return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state))) +def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: + return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations ``` - ```python -def get_current_epoch_matching_target_attestations(state: BeaconState) -> List[PendingAttestation]: +def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: return [ - a for a in state.current_epoch_attestations - if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state))) + a for a in get_matching_source_attestations(state, epoch) + if a.data.target_root == get_block_root(state, get_epoch_start_slot(epoch)) ] ``` ```python -def get_previous_epoch_matching_target_attestations(state: BeaconState) -> List[PendingAttestation]: +def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: return [ - a for a in state.previous_epoch_attestations - if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) - ] -``` - -```python -def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[PendingAttestation]: - return [ - a for a in state.previous_epoch_attestations + a for a in get_matching_source_attestations(state, epoch) if a.data.beacon_block_root == get_block_root(state, a.data.slot) ] ``` @@ -1626,8 +1601,8 @@ def get_unslashed_attesting_indices(state: BeaconState, attestations: List[Pendi ``` ```python -def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation]) -> Gwei: - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) +def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation], epoch: Epoch) -> Gwei: + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations), epoch) ``` ```python @@ -1641,8 +1616,8 @@ def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationDat ```python def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: - pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - shard_attestations = [a for a in pending_attestations if a.data.shard == shard] + attestations = get_matching_source_attestations(state, epoch) + shard_attestations = [a for a in attestations if a.data.shard == shard] shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] candidate_crosslinks = [ c for c in shard_crosslinks @@ -1655,7 +1630,7 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch return [a for a in shard_attestations if get_crosslink_from_attestation_data(state, a.data) == crosslink] # Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically) winning_crosslink = max(candidate_crosslinks, key=lambda crosslink: ( - get_attesting_balance(state, get_attestations_for(crosslink)), crosslink.crosslink_data_root + get_attesting_balance(state, get_attestations_for(crosslink), epoch), crosslink.crosslink_data_root )) return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink)) @@ -1684,13 +1659,15 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - previous_epoch_matching_target_balance = get_attesting_balance(state, get_previous_epoch_matching_target_attestations(state)) - if previous_epoch_matching_target_balance * 3 >= get_previous_epoch_total_balance(state) * 2: + epoch = get_previous_epoch(state) + previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, epoch), epoch) + if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state, epoch) * 2: state.current_justified_epoch = get_previous_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 1) - current_epoch_matching_target_balance = get_attesting_balance(state, get_current_epoch_matching_target_attestations(state)) - if current_epoch_matching_target_balance * 3 >= get_current_epoch_total_balance(state) * 2: + epoch = get_current_epoch(state) + current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, epoch), epoch) + if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state, epoch) * 2: state.current_justified_epoch = get_current_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 0) @@ -1726,9 +1703,10 @@ def process_crosslinks(state: BeaconState) -> None: previous_epoch = get_previous_epoch(state) next_epoch = get_current_epoch(state) + 1 for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)): + epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) - if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) + if 3 * get_total_balance(state, attesting_indices, epoch) >= 2 * get_total_balance(state, crosslink_committee, epoch): state.current_crosslinks[shard] = winning_crosslink ``` @@ -1737,52 +1715,53 @@ def process_crosslinks(state: BeaconState) -> None: First, we define additional helpers: ```python -def get_base_reward(state: BeaconState, total_balance: Gwei, index: ValidatorIndex) -> Gwei: +def get_base_reward(state: BeaconState, index: ValidatorIndex, epoch: Epoch) -> Gwei: + total_balance = get_total_active_balance(state, epoch) if total_balance == 0: return 0 adjusted_quotient = integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT - return get_effective_balance(state, index) // adjusted_quotient // BASE_REWARDS_PER_EPOCH + return get_effective_balance(state, index, epoch) // adjusted_quotient // BASE_REWARDS_PER_EPOCH ``` ```python -def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - previous_epoch = get_previous_epoch(state) - eligible_validators = [ +def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: + epoch = get_previous_epoch(state) + eligible_validator_indices = [ index for index, validator in enumerate(state.validator_registry) - if ( - is_active_validator(validator, previous_epoch) or - (validator.slashed and previous_epoch < validator.withdrawable_epoch) - ) + if is_active_validator(validator, epoch) or (validator.slashed and epoch < validator.withdrawable_epoch) ] rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] - for index in eligible_validators: - base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index) + for index in eligible_validator_indices: + base_reward = get_base_reward(state, index, epoch) - # Micro-incentives for matching FFG source, matching FFG target, and matching head + # Micro-incentives for attestations matching FFG source, FFG target, and head for attestations in ( - state.previous_epoch_attestations, # Matching FFG source - get_previous_epoch_matching_target_attestations(state), # Matching FFG target - get_previous_epoch_matching_head_attestations(state), # Matching head + get_matching_source_attestations(state, epoch), + get_matching_target_attestations(state, epoch), + get_matching_source_attestations(state, epoch), ): if index in get_unslashed_attesting_indices(state, attestations): - rewards[index] += base_reward * get_attesting_balance(state, attestations) // get_previous_epoch_total_balance(state) + rewards[index] += base_reward * get_attesting_balance(state, attestations, epoch) // get_total_active_balance(state, epoch) else: penalties[index] += base_reward - # Inclusion delay micro-penalty - if index in get_unslashed_attesting_indices(state, state.previous_epoch_attestations): - earliest_attestation = get_earliest_attestation(state, state.previous_epoch_attestations, index) + if index in get_unslashed_attesting_indices(state, get_matching_source_attestations(state, epoch)): + earliest_attestation = get_earliest_attestation(state, get_matching_source_attestations(state, epoch), index) + # Proposer micro-rewards + proposer_index = get_beacon_proposer_index(state, earliest_attestation.inclusion_slot) + rewards[proposer_index] += base_reward // PROPOSER_REWARD_QUOTIENT + # Inclusion delay micro-rewards inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - penalties[index] += base_reward * (inclusion_delay - MIN_ATTESTATION_INCLUSION_DELAY) // (SLOTS_PER_EPOCH - MIN_ATTESTATION_INCLUSION_DELAY) + rewards[index] += base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay # Inactivity penalty - epochs_since_finality = previous_epoch - state.finalized_epoch - if epochs_since_finality > MIN_EPOCHS_TO_INACTIVITY_PENALTY: + finality_delay = epoch - state.finalized_epoch + if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward - if index not in get_unslashed_attesting_indices(state, get_previous_epoch_matching_target_attestations(state)): - penalties[index] += get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT + if index not in get_unslashed_attesting_indices(state, get_matching_target_attestations(state, epoch)): + penalties[index] += get_effective_balance(state, index, epoch) * finality_delay // INACTIVITY_PENALTY_QUOTIENT return [rewards, penalties] ``` @@ -1792,12 +1771,13 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): + epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) - attesting_balance = get_total_balance(state, attesting_indices) - committee_balance = get_total_balance(state, crosslink_committee) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) + attesting_balance = get_total_balance(state, attesting_indices, epoch) + committee_balance = get_total_balance(state, crosslink_committee, epoch) for index in crosslink_committee: - base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index) + base_reward = get_base_reward(state, index, epoch) if index in attesting_indices: rewards[index] += base_reward * attesting_balance // committee_balance else: @@ -1812,7 +1792,7 @@ def process_rewards_and_penalties(state: BeaconState) -> None: if get_current_epoch(state) == GENESIS_EPOCH: return - rewards1, penalties1 = get_justification_and_finalization_deltas(state) + rewards1, penalties1 = get_attestation_deltas(state) rewards2, penalties2 = get_crosslink_deltas(state) for i in range(len(state.validator_registry)): increase_balance(state, i, rewards1[i] + rewards2[i]) @@ -1827,11 +1807,10 @@ Run the following function: def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validator_registry): - balance = get_balance(state, index) - if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and balance >= MAX_DEPOSIT_AMOUNT: + if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_DEPOSIT_AMOUNT: validator.activation_eligibility_epoch = get_current_epoch(state) - if is_active_validator(validator, get_current_epoch(state)) and balance < EJECTION_BALANCE: + if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance < EJECTION_BALANCE: initiate_validator_exit(state, index) # Process activations @@ -1852,7 +1831,7 @@ Run the following function: def process_slashings(state: BeaconState) -> None: current_epoch = get_current_epoch(state) active_validator_indices = get_active_validator_indices(state, current_epoch) - total_balance = get_total_balance(state, active_validator_indices) + total_balance = get_total_balance(state, active_validator_indices, current_epoch) # Compute `total_penalties` total_at_start = state.latest_slashed_balances[(current_epoch + 1) % LATEST_SLASHED_EXIT_LENGTH] @@ -1862,8 +1841,8 @@ def process_slashings(state: BeaconState) -> None: for index, validator in enumerate(state.validator_registry): if validator.slashed and current_epoch == validator.withdrawable_epoch - LATEST_SLASHED_EXIT_LENGTH // 2: penalty = max( - get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance, - get_effective_balance(state, index) // MIN_PENALTY_QUOTIENT + get_effective_balance(state, index, current_epoch) * min(total_penalties * 3, total_balance) // total_balance, + get_effective_balance(state, index, current_epoch) // MIN_PENALTY_QUOTIENT ) decrease_balance(state, index, penalty) ``` @@ -1879,6 +1858,9 @@ def process_final_updates(state: BeaconState) -> None: # Reset eth1 data votes if state.slot % SLOTS_PER_ETH1_VOTING_PERIOD == 0: state.eth1_data_votes = [] + # Update effective balances + for index, validator in enumerate(state.validator_registry): + validator.effective_balance = get_next_epoch_effective_balance(state, index) # Update start shard state.latest_start_shard = (state.latest_start_shard + get_shard_delta(state, current_epoch)) % SHARD_COUNT # Set active index root @@ -2079,18 +2061,6 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: state.previous_epoch_attestations.append(pending_attestation) ``` -Run `process_proposer_attestation_rewards(state)`. - -```python -def process_proposer_attestation_rewards(state: BeaconState) -> None: - proposer_index = get_beacon_proposer_index(state) - for pending_attestations in (state.previous_epoch_attestations, state.current_epoch_attestations): - for index in get_unslashed_attesting_indices(state, pending_attestations): - if get_earliest_attestation(state, pending_attestations, index).inclusion_slot == state.slot: - base_reward = get_base_reward(state, get_current_epoch_total_balance(state), index) - increase_balance(state, proposer_index, base_reward // PROPOSER_REWARD_QUOTIENT) -``` - ##### Deposits Verify that `len(block.body.deposits) == min(MAX_DEPOSITS, state.latest_eth1_data.deposit_count - state.deposit_index)`. @@ -2153,13 +2123,11 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, slashed=False, - high_balance=0 + effective_balance=amount - amount % HIGH_BALANCE_INCREMENT, ) - # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. state.validator_registry.append(validator) - state.balances.append(0) - set_balance(state, len(state.validator_registry) - 1, amount) + state.balances.append(amount) else: # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) @@ -2212,8 +2180,8 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: Process ``Transfer`` operation. Note that this function mutates ``state``. """ - # Verify the amount and fee aren't individually too big (for anti-overflow purposes) - assert get_balance(state, transfer.sender) >= max(transfer.amount, transfer.fee) + # Verify the amount and fee are not individually too big (for anti-overflow purposes) + assert state.balances[transfer.sender] >= max(transfer.amount, transfer.fee) # A transfer is valid in only one slot assert state.slot == transfer.slot # Only withdrawn or not-yet-deposited accounts can transfer @@ -2238,8 +2206,8 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: increase_balance(state, transfer.recipient, transfer.amount) increase_balance(state, get_beacon_proposer_index(state), transfer.fee) # Verify balances are not dust - assert not (0 < get_balance(state, transfer.sender) < MIN_DEPOSIT_AMOUNT) - assert not (0 < get_balance(state, transfer.recipient) < MIN_DEPOSIT_AMOUNT) + assert not (0 < state.balances[transfer.sender] < MIN_DEPOSIT_AMOUNT) + assert not (0 < state.balances[transfer.recipient] < MIN_DEPOSIT_AMOUNT) ``` #### State root verification diff --git a/specs/light_client/sync_protocol.md b/specs/light_client/sync_protocol.md index 900b2e64f..7b8388583 100644 --- a/specs/light_client/sync_protocol.md +++ b/specs/light_client/sync_protocol.md @@ -180,8 +180,8 @@ def verify_block_validity_proof(proof: BlockValidityProof, validator_memory: Val assert proof.shard_parent_block.beacon_chain_root == hash_tree_root(proof.header) committee = compute_committee(proof.header, validator_memory) # Verify that we have >=50% support - support_balance = sum([v.high_balance for i, v in enumerate(committee) if get_bitfield_bit(proof.shard_bitfield, i) is True]) - total_balance = sum([v.high_balance for i, v in enumerate(committee)]) + support_balance = sum([v.effective_balance for i, v in enumerate(committee) if get_bitfield_bit(proof.shard_bitfield, i) is True]) + total_balance = sum([v.effective_balance for i, v in enumerate(committee)]) assert support_balance * 2 > total_balance # Verify shard attestations group_public_key = bls_aggregate_pubkeys([ diff --git a/test_libs/pyspec/eth2spec/phase0/state_transition.py b/test_libs/pyspec/eth2spec/phase0/state_transition.py index 38ecd2a02..1bef358d4 100644 --- a/test_libs/pyspec/eth2spec/phase0/state_transition.py +++ b/test_libs/pyspec/eth2spec/phase0/state_transition.py @@ -11,7 +11,6 @@ from .spec import ( BeaconState, BeaconBlock, Slot, - process_proposer_attestation_rewards, ) @@ -52,7 +51,6 @@ def process_operations(state: BeaconState, block: BeaconBlock) -> None: spec.MAX_ATTESTATIONS, spec.process_attestation, ) - process_proposer_attestation_rewards(state) assert len(block.body.deposits) == expected_deposit_count(state) process_operation_type( diff --git a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py index 84c19145a..bcaf6fb7a 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py +++ b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py @@ -3,11 +3,11 @@ import pytest import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( - get_balance, get_beacon_proposer_index, process_attester_slashing, ) from tests.helpers import ( + get_balance, get_valid_attester_slashing, next_epoch, ) diff --git a/test_libs/pyspec/tests/block_processing/test_process_deposit.py b/test_libs/pyspec/tests/block_processing/test_process_deposit.py index 4031e650d..4fb8b3a1e 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_deposit.py +++ b/test_libs/pyspec/tests/block_processing/test_process_deposit.py @@ -4,11 +4,11 @@ import pytest import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( - get_balance, ZERO_HASH, process_deposit, ) from tests.helpers import ( + get_balance, build_deposit, privkeys, pubkeys, diff --git a/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py index 6d5f3045d..475221036 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py +++ b/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py @@ -3,11 +3,11 @@ import pytest import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( - get_balance, get_current_epoch, process_proposer_slashing, ) from tests.helpers import ( + get_balance, get_valid_proposer_slashing, ) diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 44d2dcb4d..616f3b797 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -51,6 +51,8 @@ privkeys = [i + 1 for i in range(1000)] pubkeys = [bls.privtopub(privkey) for privkey in privkeys] pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} +def get_balance(state, index): + return state.balances[index] def set_bitfield_bit(bitfield, i): """ diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index 29333a7ad..508b07905 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -16,7 +16,6 @@ from eth2spec.phase0.spec import ( VoluntaryExit, # functions get_active_validator_indices, - get_balance, get_beacon_proposer_index, get_block_root, get_current_epoch, @@ -24,7 +23,6 @@ from eth2spec.phase0.spec import ( get_state_root, advance_slot, cache_state, - set_balance, slot_to_epoch, verify_merkle_branch, hash, @@ -38,6 +36,7 @@ from eth2spec.utils.merkle_minimal import ( get_merkle_root, ) from .helpers import ( + get_balance, build_deposit_data, build_empty_block_for_next_slot, fill_aggregate_attestation, @@ -53,7 +52,6 @@ from .helpers import ( # mark entire file as 'sanity' pytestmark = pytest.mark.sanity - def check_finality(state, prev_state, current_justified_changed, @@ -304,6 +302,7 @@ def test_deposit_top_up(state): def test_attestation(state): + state.slot = spec.SLOTS_PER_EPOCH test_state = deepcopy(state) attestation = get_valid_attestation(state) @@ -318,7 +317,6 @@ def test_attestation(state): assert len(test_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1 proposer_index = get_beacon_proposer_index(test_state) - assert test_state.balances[proposer_index] > state.balances[proposer_index] # # Epoch transition should move to previous_epoch_attestations @@ -443,7 +441,7 @@ def test_balance_driven_status_transitions(state): assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH # set validator balance to below ejection threshold - set_balance(pre_state, validator_index, spec.EJECTION_BALANCE - 1) + pre_state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE - 1 post_state = deepcopy(pre_state) # From 4d26ae255a1c9127fe4cc9dc3ed2ac830b1bf1f3 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 15:31:15 +1000 Subject: [PATCH 07/26] Bug fix --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 84e9e4230..ba17691bc 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -731,10 +731,10 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> List[Valid return [i for i, v in enumerate(state.validator_registry) if is_active_validator(v, epoch)] ``` -### `get_next_epoch_effective_balance` +### `get_current_epoch_effective_balance` ```python -def get_next_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> None: +def get_current_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> None: """ Get validator effective balance for the next epoch """ @@ -1020,7 +1020,7 @@ def get_effective_balance(state: BeaconState, index: ValidatorIndex, epoch: Epoc """ Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. """ - return state.validator_registry[index].effective_balance if epoch == get_current_epoch(state) else get_next_epoch_effective_balance(state, index) + return get_current_epoch_effective_balance(state, index) if epoch == get_current_epoch(state) else state.validator_registry[index].effective_balance ``` ### `get_total_balance` @@ -1860,7 +1860,7 @@ def process_final_updates(state: BeaconState) -> None: state.eth1_data_votes = [] # Update effective balances for index, validator in enumerate(state.validator_registry): - validator.effective_balance = get_next_epoch_effective_balance(state, index) + validator.effective_balance = get_current_epoch_effective_balance(state, index) # Update start shard state.latest_start_shard = (state.latest_start_shard + get_shard_delta(state, current_epoch)) % SHARD_COUNT # Set active index root From f07b94e77c0c2453050905ea9cf8e51ada34ff2c Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 15:37:12 +1000 Subject: [PATCH 08/26] Fixes --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 27e3b8663..e5ef8af1b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -937,13 +937,13 @@ def get_beacon_proposer_index(state: BeaconState, slot: Slot=None) -> ValidatorI """ Return the beacon proposer index at ``slot``. """ - current_epoch = get_current_epoch(state) + epoch = slot_to_epoch(slot if slot != None else state.slot) first_committee, _ = get_crosslink_committees_at_slot(state, slot if slot != None else state.slot)[0] i = 0 while True: - candidate = first_committee[(current_epoch + i) % len(first_committee)] - random_byte = hash(generate_seed(state, current_epoch) + int_to_bytes8(i // 32))[i % 32] - if get_effective_balance(state, candidate, current_epoch) * 256 > MAX_DEPOSIT_AMOUNT * random_byte: + candidate = first_committee[(epoch + i) % len(first_committee)] + random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] + if get_effective_balance(state, candidate, epoch) * 256 > MAX_DEPOSIT_AMOUNT * random_byte: return candidate i += 1 ``` From d700ea44066ce3efb47f58e158bc184510f266ab Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 16:10:25 +1000 Subject: [PATCH 09/26] Fixes --- specs/core/0_beacon-chain.md | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e5ef8af1b..c1dd75fb0 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -58,6 +58,7 @@ - [`is_active_validator`](#is_active_validator) - [`is_slashable_validator`](#is_slashable_validator) - [`get_active_validator_indices`](#get_active_validator_indices) + - [`get_current_epoch_effective_balance`](#get_current_epoch_effective_balance) - [`increase_balance`](#increase_balance) - [`decrease_balance`](#decrease_balance) - [`get_permuted_index`](#get_permuted_index) @@ -712,7 +713,7 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> List[Valid ### `get_current_epoch_effective_balance` ```python -def get_current_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> None: +def get_current_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: """ Get validator effective balance for the next epoch """ @@ -1008,7 +1009,7 @@ def get_total_balance(state: BeaconState, validators: List[ValidatorIndex], epoc """ Return the combined effective balance of an array of ``validators``. """ - return sum([get_effective_balance(state, i, epoch) for i in validators]) + return sum([get_effective_balance(state, index, epoch) for index in validators]) ``` ### `get_domain` @@ -1547,6 +1548,8 @@ def process_justification_and_finalization(state: BeaconState) -> None: if get_current_epoch(state) <= GENESIS_EPOCH + 1: return + previous_epoch = get_previous_epoch(state) + current_epoch = get_current_epoch(state) old_previous_justified_epoch = state.previous_justified_epoch old_current_justified_epoch = state.current_justified_epoch @@ -1554,22 +1557,19 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - epoch = get_previous_epoch(state) - previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, epoch), epoch) - if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state, epoch) * 2: + previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch), previous_epoch) + if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state, previous_epoch) * 2: state.current_justified_epoch = get_previous_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 1) - epoch = get_current_epoch(state) - current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, epoch), epoch) - if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state, epoch) * 2: + current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch), current_epoch) + if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state, current_epoch) * 2: state.current_justified_epoch = get_current_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 0) # Process finalizations bitfield = state.justification_bitfield - current_epoch = get_current_epoch(state) # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch == current_epoch - 3: state.finalized_epoch = old_previous_justified_epoch @@ -1611,39 +1611,39 @@ First, we define additional helpers: ```python def get_base_reward(state: BeaconState, index: ValidatorIndex, epoch: Epoch) -> Gwei: - total_balance = get_total_active_balance(state, epoch) - if total_balance == 0: + adjusted_quotient = integer_squareroot(get_total_active_balance(state, epoch)) // BASE_REWARD_QUOTIENT + if adjusted_quotient == 0: return 0 - - adjusted_quotient = integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT return get_effective_balance(state, index, epoch) // adjusted_quotient // BASE_REWARDS_PER_EPOCH + ``` ```python def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - epoch = get_previous_epoch(state) + previous_epoch = get_previous_epoch(state) + total_balance = get_total_active_balance(state, previous_epoch) eligible_validator_indices = [ index for index, validator in enumerate(state.validator_registry) - if is_active_validator(validator, epoch) or (validator.slashed and epoch < validator.withdrawable_epoch) + if is_active_validator(validator, previous_epoch) or (validator.slashed and previous_epoch < validator.withdrawable_epoch) ] rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] for index in eligible_validator_indices: - base_reward = get_base_reward(state, index, epoch) + base_reward = get_base_reward(state, index, previous_epoch) # Micro-incentives for attestations matching FFG source, FFG target, and head for attestations in ( - get_matching_source_attestations(state, epoch), - get_matching_target_attestations(state, epoch), - get_matching_source_attestations(state, epoch), + get_matching_source_attestations(state, previous_epoch), + get_matching_target_attestations(state, previous_epoch), + get_matching_source_attestations(state, previous_epoch), ): if index in get_unslashed_attesting_indices(state, attestations): - rewards[index] += base_reward * get_attesting_balance(state, attestations, epoch) // get_total_active_balance(state, epoch) + rewards[index] += base_reward * get_attesting_balance(state, attestations, previous_epoch) // total_balance else: penalties[index] += base_reward - if index in get_unslashed_attesting_indices(state, get_matching_source_attestations(state, epoch)): - earliest_attestation = get_earliest_attestation(state, get_matching_source_attestations(state, epoch), index) + if index in get_unslashed_attesting_indices(state, get_matching_source_attestations(state, previous_epoch)): + earliest_attestation = get_earliest_attestation(state, get_matching_source_attestations(state, previous_epoch), index) # Proposer micro-rewards proposer_index = get_beacon_proposer_index(state, earliest_attestation.inclusion_slot) rewards[proposer_index] += base_reward // PROPOSER_REWARD_QUOTIENT @@ -1652,11 +1652,11 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: rewards[index] += base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay # Inactivity penalty - finality_delay = epoch - state.finalized_epoch + finality_delay = previous_epoch - state.finalized_epoch if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward - if index not in get_unslashed_attesting_indices(state, get_matching_target_attestations(state, epoch)): - penalties[index] += get_effective_balance(state, index, epoch) * finality_delay // INACTIVITY_PENALTY_QUOTIENT + if index not in get_unslashed_attesting_indices(state, get_matching_target_attestations(state, previous_epoch)): + penalties[index] += get_effective_balance(state, index, previous_epoch) * finality_delay // INACTIVITY_PENALTY_QUOTIENT return [rewards, penalties] ``` From 06f475a844e33d4ef13b1e4097c62b20b752bc88 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 16:32:41 +1000 Subject: [PATCH 10/26] Fixes --- specs/core/0_beacon-chain.md | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c1dd75fb0..b6b4bb13e 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -715,12 +715,12 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> List[Valid ```python def get_current_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: """ - Get validator effective balance for the next epoch + Get validator effective balance for the current epoch """ balance = min(state.balances[index], MAX_DEPOSIT_AMOUNT) validator = state.validator_registry[index] HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 - if validator.effective_balance > balance or validator.effective_balance + 3 * HALF_INCREMENT < balance: + if state.slot == GENESIS_SLOT or (validator.effective_balance > balance or validator.effective_balance + 3 * HALF_INCREMENT < balance): return balance - balance % HIGH_BALANCE_INCREMENT return validator.effective_balance ``` @@ -1705,7 +1705,7 @@ def process_registry_updates(state: BeaconState) -> None: if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_DEPOSIT_AMOUNT: validator.activation_eligibility_epoch = get_current_epoch(state) - if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance < EJECTION_BALANCE: + if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: initiate_validator_exit(state, index) # Process activations @@ -1956,30 +1956,21 @@ For each `deposit` in `block.body.deposits`, run the following function: ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: """ - Process a deposit from Ethereum 1.0. - Used to add a validator or top up an existing validator's - balance by some ``deposit`` amount. - + Process an Eth1 deposit, registering a validator or increasing its balance. Note that this function mutates ``state``. """ # Deposits must be processed in order assert deposit.index == state.deposit_index + state.deposit_index += 1 # Verify the Merkle branch - merkle_branch_is_valid = verify_merkle_branch( + assert verify_merkle_branch( leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialization proof=deposit.proof, depth=DEPOSIT_CONTRACT_TREE_DEPTH, index=deposit.index, root=state.latest_eth1_data.deposit_root, ) - assert merkle_branch_is_valid - - # Increment the next deposit index we are expecting. Note that this - # needs to be done here because while the deposit contract will never - # create an invalid Merkle branch, it may admit an invalid deposit - # object, and we need to be able to skip over it - state.deposit_index += 1 validator_pubkeys = [v.pubkey for v in state.validator_registry] pubkey = deposit.data.pubkey @@ -1998,11 +1989,11 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=amount - amount % HIGH_BALANCE_INCREMENT, ) state.validator_registry.append(validator) state.balances.append(amount) + validator.effective_balance = get_current_epoch_effective_balance(state, len(state.validator_registry) - 1) else: # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) From e184f0b3fe3a660e9a31a37eed146e73aa65f040 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 16:35:02 +1000 Subject: [PATCH 11/26] Fix --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index b6b4bb13e..be0fb5d5a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1959,10 +1959,6 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: Process an Eth1 deposit, registering a validator or increasing its balance. Note that this function mutates ``state``. """ - # Deposits must be processed in order - assert deposit.index == state.deposit_index - state.deposit_index += 1 - # Verify the Merkle branch assert verify_merkle_branch( leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialization @@ -1972,6 +1968,10 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: root=state.latest_eth1_data.deposit_root, ) + # Deposits must be processed in order + assert deposit.index == state.deposit_index + state.deposit_index += 1 + validator_pubkeys = [v.pubkey for v in state.validator_registry] pubkey = deposit.data.pubkey amount = deposit.data.amount From 7642abf1143a3be3cd74d9532a13d41440ca7f31 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 16:36:34 +1000 Subject: [PATCH 12/26] Fix| --- specs/core/0_beacon-chain.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index be0fb5d5a..a3a9aff9d 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1972,26 +1972,25 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: assert deposit.index == state.deposit_index state.deposit_index += 1 - validator_pubkeys = [v.pubkey for v in state.validator_registry] pubkey = deposit.data.pubkey amount = deposit.data.amount - + validator_pubkeys = [v.pubkey for v in state.validator_registry] if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) if not bls_verify(pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT)): return # Add new validator - validator = Validator( + state.validator_registry.append(Validator( pubkey=pubkey, withdrawal_credentials=deposit.data.withdrawal_credentials, activation_eligibility_epoch=FAR_FUTURE_EPOCH, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - ) + )) - state.validator_registry.append(validator) + # Add initial balance state.balances.append(amount) validator.effective_balance = get_current_epoch_effective_balance(state, len(state.validator_registry) - 1) else: From d6644edcc9e5f25962ada04f120fbe0d63698e3d Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Sat, 20 Apr 2019 17:12:40 +1000 Subject: [PATCH 13/26] Fix test --- specs/core/0_beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a3a9aff9d..32f24b2e3 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1981,14 +1981,15 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: return # Add new validator - state.validator_registry.append(Validator( + validator = Validator( pubkey=pubkey, withdrawal_credentials=deposit.data.withdrawal_credentials, activation_eligibility_epoch=FAR_FUTURE_EPOCH, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - )) + ) + state.validator_registry.append(validator) # Add initial balance state.balances.append(amount) From c123fb1b975ba2f2ba41e0637f70f5a693cdf2a3 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Mon, 22 Apr 2019 16:13:46 +1000 Subject: [PATCH 14/26] =?UTF-8?q?Single=20effective=20balance=20per=20revi?= =?UTF-8?q?ew=20by=20Vitalik=E2=80=94significant=20simplification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/core/0_beacon-chain.md | 106 +++++++++++++---------------------- 1 file changed, 40 insertions(+), 66 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 32f24b2e3..8739aead9 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -58,7 +58,6 @@ - [`is_active_validator`](#is_active_validator) - [`is_slashable_validator`](#is_slashable_validator) - [`get_active_validator_indices`](#get_active_validator_indices) - - [`get_current_epoch_effective_balance`](#get_current_epoch_effective_balance) - [`increase_balance`](#increase_balance) - [`decrease_balance`](#decrease_balance) - [`get_permuted_index`](#get_permuted_index) @@ -77,7 +76,6 @@ - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) - [`bytes_to_int`](#bytes_to_int) - - [`get_effective_balance`](#get_effective_balance) - [`get_total_balance`](#get_total_balance) - [`get_domain`](#get_domain) - [`get_bitfield_bit`](#get_bitfield_bit) @@ -194,7 +192,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | | `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | | `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei | -| `HIGH_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | +| `EFFECTIVE_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | ### Initial values @@ -710,21 +708,6 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> List[Valid return [i for i, v in enumerate(state.validator_registry) if is_active_validator(v, epoch)] ``` -### `get_current_epoch_effective_balance` - -```python -def get_current_epoch_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Get validator effective balance for the current epoch - """ - balance = min(state.balances[index], MAX_DEPOSIT_AMOUNT) - validator = state.validator_registry[index] - HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 - if state.slot == GENESIS_SLOT or (validator.effective_balance > balance or validator.effective_balance + 3 * HALF_INCREMENT < balance): - return balance - balance % HIGH_BALANCE_INCREMENT - return validator.effective_balance -``` - ### `increase_balance` ```python @@ -944,7 +927,7 @@ def get_beacon_proposer_index(state: BeaconState, slot: Slot=None) -> ValidatorI while True: candidate = first_committee[(epoch + i) % len(first_committee)] random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] - if get_effective_balance(state, candidate, epoch) * 256 > MAX_DEPOSIT_AMOUNT * random_byte: + if state.validator_registry[candidate].effective_balance * 256 > MAX_DEPOSIT_AMOUNT * random_byte: return candidate i += 1 ``` @@ -992,24 +975,14 @@ def bytes_to_int(data: bytes) -> int: return int.from_bytes(data, 'little') ``` -### `get_effective_balance` - -```python -def get_effective_balance(state: BeaconState, index: ValidatorIndex, epoch: Epoch) -> Gwei: - """ - Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. - """ - return get_current_epoch_effective_balance(state, index) if epoch == get_current_epoch(state) else state.validator_registry[index].effective_balance -``` - ### `get_total_balance` ```python -def get_total_balance(state: BeaconState, validators: List[ValidatorIndex], epoch: Epoch) -> Gwei: +def get_total_balance(state: BeaconState, indices: List[ValidatorIndex]) -> Gwei: """ Return the combined effective balance of an array of ``validators``. """ - return sum([get_effective_balance(state, index, epoch) for index in validators]) + return sum([state.validator_registry[index].effective_balance for index in indices]) ``` ### `get_domain` @@ -1244,9 +1217,10 @@ def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistlebl """ current_epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) - state.validator_registry[slashed_index].slashed = True - state.validator_registry[slashed_index].withdrawable_epoch = current_epoch + LATEST_SLASHED_EXIT_LENGTH - slashed_balance = get_effective_balance(state, slashed_index, current_epoch) + slashed_validator = state.validator_registry[slashed_index] + slashed_validator.slashed = True + slashed_validator.withdrawable_epoch = current_epoch + LATEST_SLASHED_EXIT_LENGTH + slashed_balance = slashed_validator.effective_balance state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance proposer_index = get_beacon_proposer_index(state) @@ -1463,8 +1437,8 @@ The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SL We define epoch transition helper functions: ```python -def get_total_active_balance(state: BeaconState, epoch: Epoch) -> Gwei: - return get_total_balance(state, get_active_validator_indices(state, epoch), epoch) +def get_total_active_balance(state: BeaconState) -> Gwei: + return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state))) ``` ```python @@ -1497,8 +1471,8 @@ def get_unslashed_attesting_indices(state: BeaconState, attestations: List[Pendi ``` ```python -def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation], epoch: Epoch) -> Gwei: - return get_total_balance(state, get_unslashed_attesting_indices(state, attestations), epoch) +def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestation]) -> Gwei: + return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) ``` ```python @@ -1526,7 +1500,7 @@ def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch return [a for a in shard_attestations if get_crosslink_from_attestation_data(state, a.data) == crosslink] # Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically) winning_crosslink = max(candidate_crosslinks, key=lambda crosslink: ( - get_attesting_balance(state, get_attestations_for(crosslink), epoch), crosslink.crosslink_data_root + get_attesting_balance(state, get_attestations_for(crosslink)), crosslink.crosslink_data_root )) return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink)) @@ -1557,13 +1531,13 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch), previous_epoch) - if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state, previous_epoch) * 2: + previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch)) + if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = get_previous_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 1) - current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch), current_epoch) - if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state, current_epoch) * 2: + current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch)) + if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = get_current_epoch(state) state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) state.justification_bitfield |= (1 << 0) @@ -1601,7 +1575,7 @@ def process_crosslinks(state: BeaconState) -> None: epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) - if 3 * get_total_balance(state, attesting_indices, epoch) >= 2 * get_total_balance(state, crosslink_committee, epoch): + if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink ``` @@ -1610,18 +1584,18 @@ def process_crosslinks(state: BeaconState) -> None: First, we define additional helpers: ```python -def get_base_reward(state: BeaconState, index: ValidatorIndex, epoch: Epoch) -> Gwei: - adjusted_quotient = integer_squareroot(get_total_active_balance(state, epoch)) // BASE_REWARD_QUOTIENT +def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: + adjusted_quotient = integer_squareroot(get_total_active_balance(state)) // BASE_REWARD_QUOTIENT if adjusted_quotient == 0: return 0 - return get_effective_balance(state, index, epoch) // adjusted_quotient // BASE_REWARDS_PER_EPOCH + return state.validator_registry[index].effective_balance // adjusted_quotient // BASE_REWARDS_PER_EPOCH ``` ```python def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: previous_epoch = get_previous_epoch(state) - total_balance = get_total_active_balance(state, previous_epoch) + total_balance = get_total_active_balance(state) eligible_validator_indices = [ index for index, validator in enumerate(state.validator_registry) if is_active_validator(validator, previous_epoch) or (validator.slashed and previous_epoch < validator.withdrawable_epoch) @@ -1629,7 +1603,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] for index in eligible_validator_indices: - base_reward = get_base_reward(state, index, previous_epoch) + base_reward = get_base_reward(state, index) # Micro-incentives for attestations matching FFG source, FFG target, and head for attestations in ( @@ -1638,7 +1612,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: get_matching_source_attestations(state, previous_epoch), ): if index in get_unslashed_attesting_indices(state, attestations): - rewards[index] += base_reward * get_attesting_balance(state, attestations, previous_epoch) // total_balance + rewards[index] += base_reward * get_attesting_balance(state, attestations) // total_balance else: penalties[index] += base_reward @@ -1656,7 +1630,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward if index not in get_unslashed_attesting_indices(state, get_matching_target_attestations(state, previous_epoch)): - penalties[index] += get_effective_balance(state, index, previous_epoch) * finality_delay // INACTIVITY_PENALTY_QUOTIENT + penalties[index] += state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT return [rewards, penalties] ``` @@ -1669,10 +1643,10 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) - attesting_balance = get_total_balance(state, attesting_indices, epoch) - committee_balance = get_total_balance(state, crosslink_committee, epoch) + attesting_balance = get_total_balance(state, attesting_indices) + committee_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: - base_reward = get_base_reward(state, index, epoch) + base_reward = get_base_reward(state, index) if index in attesting_indices: rewards[index] += base_reward * attesting_balance // committee_balance else: @@ -1726,7 +1700,7 @@ Run the following function: def process_slashings(state: BeaconState) -> None: current_epoch = get_current_epoch(state) active_validator_indices = get_active_validator_indices(state, current_epoch) - total_balance = get_total_balance(state, active_validator_indices, current_epoch) + total_balance = get_total_balance(state, active_validator_indices) # Compute `total_penalties` total_at_start = state.latest_slashed_balances[(current_epoch + 1) % LATEST_SLASHED_EXIT_LENGTH] @@ -1736,8 +1710,8 @@ def process_slashings(state: BeaconState) -> None: for index, validator in enumerate(state.validator_registry): if validator.slashed and current_epoch == validator.withdrawable_epoch - LATEST_SLASHED_EXIT_LENGTH // 2: penalty = max( - get_effective_balance(state, index, current_epoch) * min(total_penalties * 3, total_balance) // total_balance, - get_effective_balance(state, index, current_epoch) // MIN_PENALTY_QUOTIENT + validator.effective_balance * min(total_penalties * 3, total_balance) // total_balance, + validator.effective_balance // MIN_PENALTY_QUOTIENT ) decrease_balance(state, index, penalty) ``` @@ -1753,9 +1727,12 @@ def process_final_updates(state: BeaconState) -> None: # Reset eth1 data votes if state.slot % SLOTS_PER_ETH1_VOTING_PERIOD == 0: state.eth1_data_votes = [] - # Update effective balances + # Update effective balances with hysteresis for index, validator in enumerate(state.validator_registry): - validator.effective_balance = get_current_epoch_effective_balance(state, index) + balance = min(state.balances[index], MAX_DEPOSIT_AMOUNT) + HALF_INCREMENT = EFFECTIVE_BALANCE_INCREMENT // 2 + if balance < validator.effective_balance or validator.effective_balance + 3 * HALF_INCREMENT < balance: + validator.effective_balance = balance - balance % EFFECTIVE_BALANCE_INCREMENT # Update start shard state.latest_start_shard = (state.latest_start_shard + get_shard_delta(state, current_epoch)) % SHARD_COUNT # Set active index root @@ -1980,20 +1957,17 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: if not bls_verify(pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT)): return - # Add new validator - validator = Validator( + # Add validator and balance entries + state.validator_registry.append(Validator( pubkey=pubkey, withdrawal_credentials=deposit.data.withdrawal_credentials, activation_eligibility_epoch=FAR_FUTURE_EPOCH, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - ) - state.validator_registry.append(validator) - - # Add initial balance + effective_balance=amount - amount % EFFECTIVE_BALANCE_INCREMENT + )) state.balances.append(amount) - validator.effective_balance = get_current_epoch_effective_balance(state, len(state.validator_registry) - 1) else: # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) From 6903f2eec738a3f486e9704e96ed62708271c0e1 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 22 Apr 2019 16:17:14 +1000 Subject: [PATCH 15/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 8739aead9..0adee0f95 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1965,7 +1965,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=amount - amount % EFFECTIVE_BALANCE_INCREMENT + effective_balance=amount - amount % EFFECTIVE_BALANCE_INCREMENT, )) state.balances.append(amount) else: From 81ee59bca85350fd479a7833045b857284c56834 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 22 Apr 2019 16:34:50 +1000 Subject: [PATCH 16/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 0adee0f95..931511b36 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -238,7 +238,7 @@ These configurations are updated for releases, but may be out of sync during `de | `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) | | `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) | | `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) | -| `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) | +| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) | * The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch. * The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. @@ -401,7 +401,7 @@ The types are defined topologically to aid in facilitating an executable version 'withdrawable_epoch': 'uint64', # Was the validator slashed 'slashed': 'bool', - # Rounded balance + # Effective balance 'effective_balance': 'uint64', } ``` @@ -725,7 +725,7 @@ def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> """ Decrease validator balance by ``delta`` with underflow protection. """ - state.balances[index] = state.balances[index] - delta if state.balances[index] >= delta else 0 + state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta ``` ### `get_permuted_index` @@ -925,10 +925,12 @@ def get_beacon_proposer_index(state: BeaconState, slot: Slot=None) -> ValidatorI first_committee, _ = get_crosslink_committees_at_slot(state, slot if slot != None else state.slot)[0] i = 0 while True: - candidate = first_committee[(epoch + i) % len(first_committee)] + candidate_index = first_committee[(epoch + i) % len(first_committee)] random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] - if state.validator_registry[candidate].effective_balance * 256 > MAX_DEPOSIT_AMOUNT * random_byte: - return candidate + MAX_RANDOM_BYTE = 2**8 - 1 + effective_balance = state.validator_registry[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_DEPOSIT_AMOUNT * random_byte: + return candidate_index i += 1 ``` @@ -1217,10 +1219,9 @@ def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistlebl """ current_epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) - slashed_validator = state.validator_registry[slashed_index] - slashed_validator.slashed = True - slashed_validator.withdrawable_epoch = current_epoch + LATEST_SLASHED_EXIT_LENGTH - slashed_balance = slashed_validator.effective_balance + state.validator_registry[slashed_index].slashed = True + state.validator_registry[slashed_index].withdrawable_epoch = current_epoch + LATEST_SLASHED_EXIT_LENGTH + slashed_balance = state.validator_registry[slashed_index].effective_balance state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance proposer_index = get_beacon_proposer_index(state) @@ -1371,9 +1372,7 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) active_validator_indices = get_active_validator_indices(validators, slot_to_epoch(start_state.slot)) attestation_targets = [(i, get_latest_attestation_target(store, i)) for i in active_validator_indices] - # Use the rounded-balance-with-hysteresis supplied by the protocol for fork - # choice voting. This reduces the number of recomputations that need to be - # made for optimized implementations that precompute and save data + # Use the effective balance for fork choice voting to reduce recomputations and save bandwidth def get_vote_count(block: BeaconBlock) -> int: return sum( start_state.validator_registry[validator_index].effective_balance @@ -1711,7 +1710,7 @@ def process_slashings(state: BeaconState) -> None: if validator.slashed and current_epoch == validator.withdrawable_epoch - LATEST_SLASHED_EXIT_LENGTH // 2: penalty = max( validator.effective_balance * min(total_penalties * 3, total_balance) // total_balance, - validator.effective_balance // MIN_PENALTY_QUOTIENT + validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT ) decrease_balance(state, index, penalty) ``` @@ -1965,7 +1964,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=amount - amount % EFFECTIVE_BALANCE_INCREMENT, + effective_balance=amount - amount % EFFECTIVE_BALANCE_INCREMENT )) state.balances.append(amount) else: From f2d885f0d8e652ab48aa9dfb91305f7e15d65e3c Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 24 Apr 2019 14:23:51 +1000 Subject: [PATCH 17/26] Address Danny's comments --- specs/core/0_beacon-chain.md | 125 ++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 931511b36..f61eb2ec5 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -66,8 +66,8 @@ - [`get_shard_delta`](#get_shard_delta) - [`compute_committee`](#compute_committee) - [`get_crosslink_committees_at_slot`](#get_crosslink_committees_at_slot) + - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) - - [`get_state_root`](#get_state_root) - [`get_randao_mix`](#get_randao_mix) - [`get_active_index_root`](#get_active_index_root) - [`generate_seed`](#generate_seed) @@ -416,6 +416,8 @@ The types are defined topologically to aid in facilitating an executable version 'data': AttestationData, # Inclusion slot 'inclusion_slot': 'uint64', + # Proposer index + 'proposer_index': 'uint64', } ``` @@ -678,6 +680,7 @@ def get_epoch_start_slot(epoch: Epoch) -> Slot: ``` ### `is_active_validator` + ```python def is_active_validator(validator: Validator, epoch: Epoch) -> bool: """ @@ -687,15 +690,14 @@ def is_active_validator(validator: Validator, epoch: Epoch) -> bool: ``` ### `is_slashable_validator` + ```python def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: """ Check if ``validator`` is slashable. """ - return ( - validator.activation_epoch <= epoch < validator.withdrawable_epoch and - validator.slashed is False - ) + return validator.slashed is False and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + ``` ### `get_active_validator_indices` @@ -772,12 +774,12 @@ def get_epoch_committee_count(state: BeaconState, epoch: Epoch) -> int: """ Return the number of committees in one epoch. """ - active_validators = get_active_validator_indices(state, epoch) + active_validator_indices = get_active_validator_indices(state, epoch) return max( 1, min( SHARD_COUNT // SLOTS_PER_EPOCH, - len(active_validators) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + len(active_validator_indices) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, ) ) * SLOTS_PER_EPOCH ``` @@ -850,11 +852,11 @@ def get_crosslink_committees_at_slot(state: BeaconState, ] ``` -### `get_block_root` +### `get_block_root_at_slot` ```python -def get_block_root(state: BeaconState, - slot: Slot) -> Bytes32: +def get_block_root_at_slot(state: BeaconState, + slot: Slot) -> Bytes32: """ Return the block root at a recent ``slot``. """ @@ -862,19 +864,17 @@ def get_block_root(state: BeaconState, return state.latest_block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] ``` -`get_block_root(_, s)` should always return `signing_root` of the block in the beacon chain at slot `s`, and `get_crosslink_committees_at_slot(_, s)` should not change unless the [validator](#dfn-validator) registry changes. - -### `get_state_root` +### `get_block_root` ```python -def get_state_root(state: BeaconState, - slot: Slot) -> Bytes32: +def get_block_root(state: BeaconState, + epoch: Epoch) -> Bytes32: """ - Return the state root at a recent ``slot``. + Return the block root at a recent ``epoch``. """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] + return get_block_root_at_slot(state, get_epoch_start_slot(epoch)) ``` + ### `get_randao_mix` ```python @@ -917,17 +917,17 @@ def generate_seed(state: BeaconState, ### `get_beacon_proposer_index` ```python -def get_beacon_proposer_index(state: BeaconState, slot: Slot=None) -> ValidatorIndex: +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: """ Return the beacon proposer index at ``slot``. """ - epoch = slot_to_epoch(slot if slot != None else state.slot) - first_committee, _ = get_crosslink_committees_at_slot(state, slot if slot != None else state.slot)[0] + epoch = slot_to_epoch(state.slot) + first_committee, _ = get_crosslink_committees_at_slot(state, state.slot)[0] + MAX_RANDOM_BYTE = 2**8 - 1 i = 0 while True: candidate_index = first_committee[(epoch + i) % len(first_committee)] random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] - MAX_RANDOM_BYTE = 2**8 - 1 effective_balance = state.validator_registry[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_DEPOSIT_AMOUNT * random_byte: return candidate_index @@ -1449,7 +1449,7 @@ def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> List[P def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: return [ a for a in get_matching_source_attestations(state, epoch) - if a.data.target_root == get_block_root(state, get_epoch_start_slot(epoch)) + if a.data.target_root == get_block_root(state, epoch) ] ``` @@ -1457,7 +1457,7 @@ def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> List[P def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: return [ a for a in get_matching_source_attestations(state, epoch) - if a.data.beacon_block_root == get_block_root(state, a.data.slot) + if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) ] ``` @@ -1484,7 +1484,7 @@ def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationDat ``` ```python -def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: +def get_winning_crosslink_and_attesting_indices(state: BeaconState, shard: Shard, epoch: Epoch) -> Tuple[Crosslink, List[ValidatorIndex]]: attestations = get_matching_source_attestations(state, epoch) shard_attestations = [a for a in attestations if a.data.shard == shard] shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] @@ -1533,12 +1533,12 @@ def process_justification_and_finalization(state: BeaconState) -> None: previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch)) if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = get_previous_epoch(state) - state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) + state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 1) current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch)) if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: state.current_justified_epoch = get_current_epoch(state) - state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) + state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 0) # Process finalizations @@ -1546,19 +1546,19 @@ def process_justification_and_finalization(state: BeaconState) -> None: # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch == current_epoch - 3: state.finalized_epoch = old_previous_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) # The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch == current_epoch - 2: state.finalized_epoch = old_previous_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) # The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch == current_epoch - 2: state.finalized_epoch = old_current_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) # The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch == current_epoch - 1: state.finalized_epoch = old_current_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) ``` #### Crosslinks @@ -1573,7 +1573,7 @@ def process_crosslinks(state: BeaconState) -> None: for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)): epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink ``` @@ -1595,40 +1595,40 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: previous_epoch = get_previous_epoch(state) total_balance = get_total_active_balance(state) - eligible_validator_indices = [ - index for index, validator in enumerate(state.validator_registry) - if is_active_validator(validator, previous_epoch) or (validator.slashed and previous_epoch < validator.withdrawable_epoch) - ] rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] - for index in eligible_validator_indices: - base_reward = get_base_reward(state, index) + eligible_validator_indices = [ + index for index, v in enumerate(state.validator_registry) + if is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch) + ] - # Micro-incentives for attestations matching FFG source, FFG target, and head - for attestations in ( - get_matching_source_attestations(state, previous_epoch), - get_matching_target_attestations(state, previous_epoch), - get_matching_source_attestations(state, previous_epoch), - ): - if index in get_unslashed_attesting_indices(state, attestations): - rewards[index] += base_reward * get_attesting_balance(state, attestations) // total_balance + # Micro-incentives for matching FFG source, FFG target, and head + matching_source_attestations = get_matching_source_attestations(state, previous_epoch) + matching_target_attestations = get_matching_target_attestations(state, previous_epoch) + matching_head_attestations = get_matching_head_attestations(state, previous_epoch) + for attestations in (matching_source_attestations, matching_target_attestations, matching_head_attestations): + unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) + attesting_balance = get_attesting_balance(state, attestations) + for index in eligible_validator_indices: + if index in unslashed_attesting_indices: + rewards[index] += get_base_reward(state, index) * attesting_balance // total_balance else: - penalties[index] += base_reward + penalties[index] += get_base_reward(state, index) - if index in get_unslashed_attesting_indices(state, get_matching_source_attestations(state, previous_epoch)): - earliest_attestation = get_earliest_attestation(state, get_matching_source_attestations(state, previous_epoch), index) - # Proposer micro-rewards - proposer_index = get_beacon_proposer_index(state, earliest_attestation.inclusion_slot) - rewards[proposer_index] += base_reward // PROPOSER_REWARD_QUOTIENT - # Inclusion delay micro-rewards - inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - rewards[index] += base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay + # Proposer and inclusion delay micro-rewards + if index in get_unslashed_attesting_indices(state, matching_source_attestations): + earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) + rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT + inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot + rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay - # Inactivity penalty - finality_delay = previous_epoch - state.finalized_epoch - if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: - penalties[index] += BASE_REWARDS_PER_EPOCH * base_reward - if index not in get_unslashed_attesting_indices(state, get_matching_target_attestations(state, previous_epoch)): + # Inactivity penalty + finality_delay = previous_epoch - state.finalized_epoch + if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: + matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) + for index in eligible_validator_indices: + penalties[index] += BASE_REWARDS_PER_EPOCH * get_base_reward(state, index) + if index not in matching_target_attesting_indices: penalties[index] += state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT return [rewards, penalties] @@ -1641,7 +1641,7 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, epoch, shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) attesting_balance = get_total_balance(state, attesting_indices) committee_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: @@ -1915,7 +1915,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: pending_attestation = PendingAttestation( data=data, aggregation_bitfield=attestation.aggregation_bitfield, - inclusion_slot=state.slot + inclusion_slot=state.slot, + proposer_index=get_beacon_proposer_index(state), ) if target_epoch == get_current_epoch(state): state.current_epoch_attestations.append(pending_attestation) From 5587c44abe725afccd08fdad1667a03c0ada87f2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 24 Apr 2019 14:29:35 +1000 Subject: [PATCH 18/26] Update test_libs/pyspec/tests/test_sanity.py Co-Authored-By: JustinDrake --- test_libs/pyspec/tests/test_sanity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index f87a7c808..101bcc89c 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -436,7 +436,7 @@ def test_balance_driven_status_transitions(state): assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH # set validator balance to below ejection threshold - pre_state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE - 1 + pre_state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE post_state = deepcopy(pre_state) # From df64eeefa07eb58364fe1eb86eb351f63bf32e86 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 24 Apr 2019 14:46:28 +1000 Subject: [PATCH 19/26] Start fixing tests --- specs/core/0_beacon-chain.md | 8 ++++---- .../tests/block_processing/test_process_transfer.py | 10 ++++------ test_libs/pyspec/tests/helpers.py | 1 - 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 033e3b2cb..159d2d839 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -931,7 +931,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: candidate_index = first_committee[(current_epoch + i) % len(first_committee)] random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] effective_balance = state.validator_registry[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_DEPOSIT_AMOUNT * random_byte: + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index i += 1 ``` @@ -1291,7 +1291,7 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], # Process genesis activations for index, validator in enumerate(state.validator_registry): - if validator.effective_balance >= MAX_DEPOSIT_AMOUNT: + if validator.effective_balance >= MAX_EFFECTIVE_BALANCE: validator.activation_eligibility_epoch = GENESIS_EPOCH validator.activation_epoch = GENESIS_EPOCH @@ -1662,7 +1662,7 @@ Run the following function: def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validator_registry): - if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_DEPOSIT_AMOUNT: + if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_EFFECTIVE_BALANCE: validator.activation_eligibility_epoch = get_current_epoch(state) if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: @@ -1717,7 +1717,7 @@ def process_final_updates(state: BeaconState) -> None: state.eth1_data_votes = [] # Update effective balances with hysteresis for index, validator in enumerate(state.validator_registry): - balance = min(state.balances[index], MAX_DEPOSIT_AMOUNT) + balance = min(state.balances[index], MAX_EFFECTIVE_BALANCE) HALF_INCREMENT = EFFECTIVE_BALANCE_INCREMENT // 2 if balance < validator.effective_balance or validator.effective_balance + 3 * HALF_INCREMENT < balance: validator.effective_balance = balance - balance % EFFECTIVE_BALANCE_INCREMENT diff --git a/test_libs/pyspec/tests/block_processing/test_process_transfer.py b/test_libs/pyspec/tests/block_processing/test_process_transfer.py index 71d0894bd..65df822de 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_transfer.py +++ b/test_libs/pyspec/tests/block_processing/test_process_transfer.py @@ -5,11 +5,9 @@ import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( get_active_validator_indices, - get_balance, get_beacon_proposer_index, get_current_epoch, process_transfer, - set_balance, ) from tests.helpers import ( get_valid_transfer, @@ -75,7 +73,7 @@ def test_success_withdrawable(state): def test_success_active_above_max_effective(state): sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] amount = spec.MAX_EFFECTIVE_BALANCE // 32 - set_balance(state, sender_index, spec.MAX_EFFECTIVE_BALANCE + amount) + state.validator_registry[sender_index] = spec.MAX_EFFECTIVE_BALANCE + amount transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0) pre_state, post_state = run_transfer_processing(state, transfer) @@ -86,7 +84,7 @@ def test_success_active_above_max_effective(state): def test_active_but_transfer_past_effective_balance(state): sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] amount = spec.MAX_EFFECTIVE_BALANCE // 32 - set_balance(state, sender_index, spec.MAX_EFFECTIVE_BALANCE) + state.validator_registry[sender_index] = spec.MAX_EFFECTIVE_BALANCE transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0) pre_state, post_state = run_transfer_processing(state, transfer, False) @@ -107,7 +105,7 @@ def test_incorrect_slot(state): def test_insufficient_balance(state): sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] amount = spec.MAX_EFFECTIVE_BALANCE - set_balance(state, sender_index, spec.MAX_EFFECTIVE_BALANCE) + state.validator_registry[sender_index] = spec.MAX_EFFECTIVE_BALANCE transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount + 1, fee=0) # un-activate so validator can transfer @@ -140,4 +138,4 @@ def test_invalid_pubkey(state): pre_state, post_state = run_transfer_processing(state, transfer, False) - return pre_state, transfer, post_state \ No newline at end of file + return pre_state, transfer, post_state diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 162c6fa62..465825c35 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -26,7 +26,6 @@ from eth2spec.phase0.spec import ( # functions convert_to_indexed, get_active_validator_indices, - get_balance, get_attesting_indices, get_block_root, get_crosslink_committees_at_slot, From 55f042aa71c8a6a3f62e803077ecb77f33a3b432 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 24 Apr 2019 15:17:25 +1000 Subject: [PATCH 20/26] More fixes --- specs/core/0_beacon-chain.md | 4 ++-- test_libs/pyspec/tests/helpers.py | 9 ++++++--- test_libs/pyspec/tests/test_sanity.py | 16 ++++++++++++---- tests/phase0/helpers.py | 3 ++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 159d2d839..3dd4c944c 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -929,7 +929,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: i = 0 while True: candidate_index = first_committee[(current_epoch + i) % len(first_committee)] - random_byte = hash(generate_seed(state, epoch) + int_to_bytes8(i // 32))[i % 32] + random_byte = hash(generate_seed(state, current_epoch) + int_to_bytes8(i // 32))[i % 32] effective_balance = state.validator_registry[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index @@ -2011,7 +2011,7 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: assert ( state.validator_registry[transfer.sender].activation_eligibility_epoch == FAR_FUTURE_EPOCH or get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or - transfer.amount + transfer.fee + MAX_EFFECTIVE_BALANCE <= get_balance(state, transfer.sender) + transfer.amount + transfer.fee + MAX_EFFECTIVE_BALANCE <= state.balances[transfer.sender] ) # Verify that the pubkey is valid assert ( diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index 465825c35..cf3d2624a 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -28,6 +28,7 @@ from eth2spec.phase0.spec import ( get_active_validator_indices, get_attesting_indices, get_block_root, + get_block_root_at_slot, get_crosslink_committees_at_slot, get_current_epoch, get_domain, @@ -51,9 +52,11 @@ privkeys = [i + 1 for i in range(1000)] pubkeys = [bls.privtopub(privkey) for privkey in privkeys] pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} + def get_balance(state, index): return state.balances[index] + def set_bitfield_bit(bitfield, i): """ Set the bit in ``bitfield`` at position ``i`` to ``1``. @@ -151,16 +154,16 @@ def build_attestation_data(state, slot, shard): if slot == state.slot: block_root = build_empty_block_for_next_slot(state).previous_block_root else: - block_root = get_block_root(state, slot) + block_root = get_block_root_at_slot(state, slot) current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state)) if slot < current_epoch_start_slot: print(slot) - epoch_boundary_root = get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) + epoch_boundary_root = get_block_root(state, get_previous_epoch(state)) elif slot == current_epoch_start_slot: epoch_boundary_root = block_root else: - epoch_boundary_root = get_block_root(state, current_epoch_start_slot) + epoch_boundary_root = get_block_root(state, get_current_epoch(state)) if slot < current_epoch_start_slot: justified_epoch = state.previous_justified_epoch diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index 182c9cfe0..b54b5f75a 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -9,6 +9,7 @@ from eth2spec.utils.minimal_ssz import signing_root from eth2spec.phase0.spec import ( # constants ZERO_HASH, + SLOTS_PER_HISTORICAL_ROOT, # SSZ Deposit, Transfer, @@ -17,9 +18,9 @@ from eth2spec.phase0.spec import ( get_active_validator_indices, get_beacon_proposer_index, get_block_root, + get_block_root_at_slot, get_current_epoch, get_domain, - get_state_root, advance_slot, cache_state, slot_to_epoch, @@ -48,6 +49,13 @@ from .helpers import ( ) +def get_state_root(state, slot) -> bytes: + """ + Return the state root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] + # mark entire file as 'sanity' pytestmark = pytest.mark.sanity @@ -94,7 +102,7 @@ def test_empty_block_transition(state): state_transition(test_state, block) assert len(test_state.eth1_data_votes) == len(state.eth1_data_votes) + 1 - assert get_block_root(test_state, state.slot) == block.previous_block_root + assert get_block_root_at_slot(test_state, state.slot) == block.previous_block_root return state, [block], test_state @@ -108,7 +116,7 @@ def test_skipped_slots(state): assert test_state.slot == block.slot for slot in range(state.slot, test_state.slot): - assert get_block_root(test_state, slot) == block.previous_block_root + assert get_block_root_at_slot(test_state, slot) == block.previous_block_root return state, [block], test_state @@ -122,7 +130,7 @@ def test_empty_epoch_transition(state): assert test_state.slot == block.slot for slot in range(state.slot, test_state.slot): - assert get_block_root(test_state, slot) == block.previous_block_root + assert get_block_root_at_slot(test_state, slot) == block.previous_block_root return state, [block], test_state diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index 20054b821..c042dec91 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -16,6 +16,7 @@ from build.phase0.spec import ( VoluntaryExit, # functions get_block_root, + get_block_root_at_slot, get_current_epoch, get_domain, get_empty_block, @@ -141,7 +142,7 @@ def build_attestation_data(state, slot, shard): if epoch_start_slot == slot: epoch_boundary_root = block_root else: - get_block_root(state, epoch_start_slot) + get_block_root(state, get_current_epoch(state)) if slot < epoch_start_slot: justified_block_root = state.previous_justified_root From c37789dc5d5085b535d8d71ae2dc608fdb2fdd7d Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 24 Apr 2019 15:27:47 +1000 Subject: [PATCH 21/26] Tests fixed --- specs/core/0_beacon-chain.md | 4 ++-- .../pyspec/tests/block_processing/test_process_transfer.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3dd4c944c..295064f77 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -921,9 +921,9 @@ def generate_seed(state: BeaconState, ```python def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: """ - Return the beacon proposer index at ``slot``. + Return the beacon proposer index at ``state.slot``. """ - current_epoch = slot_to_epoch(state.slot) + current_epoch = get_current_epoch(state) first_committee, _ = get_crosslink_committees_at_slot(state, state.slot)[0] MAX_RANDOM_BYTE = 2**8 - 1 i = 0 diff --git a/test_libs/pyspec/tests/block_processing/test_process_transfer.py b/test_libs/pyspec/tests/block_processing/test_process_transfer.py index 65df822de..0eeaa7792 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_transfer.py +++ b/test_libs/pyspec/tests/block_processing/test_process_transfer.py @@ -73,7 +73,7 @@ def test_success_withdrawable(state): def test_success_active_above_max_effective(state): sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] amount = spec.MAX_EFFECTIVE_BALANCE // 32 - state.validator_registry[sender_index] = spec.MAX_EFFECTIVE_BALANCE + amount + state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + amount transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0) pre_state, post_state = run_transfer_processing(state, transfer) @@ -84,7 +84,7 @@ def test_success_active_above_max_effective(state): def test_active_but_transfer_past_effective_balance(state): sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] amount = spec.MAX_EFFECTIVE_BALANCE // 32 - state.validator_registry[sender_index] = spec.MAX_EFFECTIVE_BALANCE + state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0) pre_state, post_state = run_transfer_processing(state, transfer, False) @@ -105,7 +105,7 @@ def test_incorrect_slot(state): def test_insufficient_balance(state): sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] amount = spec.MAX_EFFECTIVE_BALANCE - state.validator_registry[sender_index] = spec.MAX_EFFECTIVE_BALANCE + state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount + 1, fee=0) # un-activate so validator can transfer From b361fdb385e1fd4d025f1e342e2f9d303f4bf642 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 24 Apr 2019 15:29:46 +1000 Subject: [PATCH 22/26] bug --- specs/core/0_beacon-chain.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 295064f77..c5f13710b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1603,11 +1603,12 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: penalties[index] += get_base_reward(state, index) # Proposer and inclusion delay micro-rewards - if index in get_unslashed_attesting_indices(state, matching_source_attestations): - earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) - rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay + for index in eligible_validator_indices: + if index in get_unslashed_attesting_indices(state, matching_source_attestations): + earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) + rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT + inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot + rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay # Inactivity penalty finality_delay = previous_epoch - state.finalized_epoch From 4734b2288348fb9e9a2b1cd803872f4e61a8be42 Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Wed, 24 Apr 2019 15:32:43 +1000 Subject: [PATCH 23/26] simplify --- specs/core/0_beacon-chain.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c5f13710b..4a9dcc26c 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1603,12 +1603,11 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: penalties[index] += get_base_reward(state, index) # Proposer and inclusion delay micro-rewards - for index in eligible_validator_indices: - if index in get_unslashed_attesting_indices(state, matching_source_attestations): - earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) - rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT - inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay + for index in get_unslashed_attesting_indices(state, matching_source_attestations): + earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) + rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT + inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot + rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay # Inactivity penalty finality_delay = previous_epoch - state.finalized_epoch From 9c2fa02658d7020b7d858770d70070378f3856d2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 24 Apr 2019 20:54:39 +1000 Subject: [PATCH 24/26] Update test_libs/pyspec/tests/test_sanity.py Co-Authored-By: JustinDrake --- test_libs/pyspec/tests/test_sanity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index b54b5f75a..071f3f8b3 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -323,7 +323,6 @@ def test_attestation(state): assert len(test_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1 - proposer_index = get_beacon_proposer_index(test_state) # # Epoch transition should move to previous_epoch_attestations From b1e1510e213cabe4cec2322d7bf4f5334919d75a Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 24 Apr 2019 20:57:31 +1000 Subject: [PATCH 25/26] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4a9dcc26c..cf051b5e3 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1429,6 +1429,7 @@ def get_total_active_balance(state: BeaconState) -> Gwei: ```python def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: + assert epoch in (get_current_epoch(state), get_previous_epoch(state)) return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations ``` @@ -1472,8 +1473,7 @@ def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationDat ```python def get_winning_crosslink_and_attesting_indices(state: BeaconState, shard: Shard, epoch: Epoch) -> Tuple[Crosslink, List[ValidatorIndex]]: - attestations = get_matching_source_attestations(state, epoch) - shard_attestations = [a for a in attestations if a.data.shard == shard] + shard_attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.shard == shard] shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] candidate_crosslinks = [ c for c in shard_crosslinks From 20d65e040b4fc140da5441aba4540a8618a6a482 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 24 Apr 2019 11:31:24 -0600 Subject: [PATCH 26/26] pr feedback --- specs/core/0_beacon-chain.md | 17 +++++++++++++++-- specs/validator/0_beacon-chain-validator.md | 4 ++-- test_libs/pyspec/tests/test_sanity.py | 11 +---------- tests/phase0/helpers.py | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index cf051b5e3..8eebc1618 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -68,6 +68,7 @@ - [`get_crosslink_committees_at_slot`](#get_crosslink_committees_at_slot) - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) + - [`get_state_root`](#get_state_root) - [`get_randao_mix`](#get_randao_mix) - [`get_active_index_root`](#get_active_index_root) - [`generate_seed`](#generate_seed) @@ -877,6 +878,18 @@ def get_block_root(state: BeaconState, return get_block_root_at_slot(state, get_epoch_start_slot(epoch)) ``` +### `get_state_root` + +```python +def get_state_root(state: BeaconState, + slot: Slot) -> Bytes32: + """ + Return the state root at a recent ``slot``. + """ + assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT + return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] +``` + ### `get_randao_mix` ```python @@ -1519,12 +1532,12 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch)) if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: - state.current_justified_epoch = get_previous_epoch(state) + state.current_justified_epoch = previous_epoch state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 1) current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch)) if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: - state.current_justified_epoch = get_current_epoch(state) + state.current_justified_epoch = current_epoch state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 0) diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index 03d5b5f5b..8cc38eebf 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -138,7 +138,7 @@ A validator has two primary responsibilities to the beacon chain -- [proposing b ### Block proposal -A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `get_beacon_proposer_index(state, slot)` returns the validator's `validator_index`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator is to create, sign, and broadcast a `block` that is a child of `parent` and that executes a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function). +A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `get_beacon_proposer_index(state)` returns the validator's `validator_index`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator is to create, sign, and broadcast a `block` that is a child of `parent` and that executes a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function). There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312500 validators = 10 million ETH, that's once per ~3 weeks). @@ -368,7 +368,7 @@ def get_committee_assignment( return assignment ``` -A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run during the slot in question and can not reliably be used to predict in advance. +A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run during the slot in question. Proposer selection is only stable within the context of the current epoch. ```python def is_proposer_at_slot(state: BeaconState, diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index 071f3f8b3..d3035576b 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -9,7 +9,6 @@ from eth2spec.utils.minimal_ssz import signing_root from eth2spec.phase0.spec import ( # constants ZERO_HASH, - SLOTS_PER_HISTORICAL_ROOT, # SSZ Deposit, Transfer, @@ -17,13 +16,12 @@ from eth2spec.phase0.spec import ( # functions get_active_validator_indices, get_beacon_proposer_index, - get_block_root, get_block_root_at_slot, + get_state_root, get_current_epoch, get_domain, advance_slot, cache_state, - slot_to_epoch, verify_merkle_branch, hash, ) @@ -49,13 +47,6 @@ from .helpers import ( ) -def get_state_root(state, slot) -> bytes: - """ - Return the state root at a recent ``slot``. - """ - assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT - return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] - # mark entire file as 'sanity' pytestmark = pytest.mark.sanity diff --git a/tests/phase0/helpers.py b/tests/phase0/helpers.py index c042dec91..18898dd3c 100644 --- a/tests/phase0/helpers.py +++ b/tests/phase0/helpers.py @@ -142,7 +142,7 @@ def build_attestation_data(state, slot, shard): if epoch_start_slot == slot: epoch_boundary_root = block_root else: - get_block_root(state, get_current_epoch(state)) + epoch_boundary_root = get_block_root(state, get_current_epoch(state)) if slot < epoch_start_slot: justified_block_root = state.previous_justified_root