Status code / slot reorganization

* Added `activation_slot`, `exit_slot`, `penalized_slot`, `withdrawal_slot`, use these to determine if a validator is active
* Universal min activation/exit delay of 256 slots
* Min exit time of ~1 day, but penalization delays this to ~18 days
* Penalty calculation period of `[time penalized - 18 days, time penalized + 18 days]`; made the total penalties array fixed size and wraparound to make calculation more fine-grained
* Processes withdrawals in all epochs, not just dynasty-changing epochs
* Change `get_shuffling` function to take slot as argument

Not yet done:

* Removed `shard_committees` from the state
* Removed persistent committees from the state
This commit is contained in:
vbuterin 2018-12-28 12:04:03 -06:00 committed by GitHub
parent 6b84dae09e
commit d9e897bbcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 131 additions and 106 deletions

View File

@ -66,7 +66,7 @@
- [`get_active_validator_indices`](#get_active_validator_indices) - [`get_active_validator_indices`](#get_active_validator_indices)
- [`shuffle`](#shuffle) - [`shuffle`](#shuffle)
- [`split`](#split) - [`split`](#split)
- [`get_new_shuffling`](#get_new_shuffling) - [`get_shuffling`](#get_shuffling)
- [`get_shard_committees_at_slot`](#get_shard_committees_at_slot) - [`get_shard_committees_at_slot`](#get_shard_committees_at_slot)
- [`get_block_root`](#get_block_root) - [`get_block_root`](#get_block_root)
- [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`get_beacon_proposer_index`](#get_beacon_proposer_index)
@ -168,6 +168,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
| `MAX_CASPER_VOTES` | `2**10` (= 1,024) | votes | | `MAX_CASPER_VOTES` | `2**10` (= 1,024) | votes |
| `LATEST_BLOCK_ROOTS_LENGTH` | `2**13` (= 8,192) | block roots | | `LATEST_BLOCK_ROOTS_LENGTH` | `2**13` (= 8,192) | block roots |
| `LATEST_RANDAO_MIXES_LENGTH` | `2**13` (= 8,192) | randao mixes | | `LATEST_RANDAO_MIXES_LENGTH` | `2**13` (= 8,192) | randao mixes |
| `LATEST_PENALIZED_EXIT_LENGTH` | `2**13` (= 8,192) | cycles | ~36 days |
| `EMPTY_SIGNATURE` | `[bytes48(0), bytes48(0)]` | - | | `EMPTY_SIGNATURE` | `[bytes48(0), bytes48(0)]` | - |
* 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 `EPOCH_LENGTH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) * 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 `EPOCH_LENGTH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
@ -188,6 +189,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
| `INITIAL_FORK_VERSION` | `0` | | `INITIAL_FORK_VERSION` | `0` |
| `INITIAL_SLOT_NUMBER` | `0` | | `INITIAL_SLOT_NUMBER` | `0` |
| `ZERO_HASH` | `bytes([0] * 32)` | | `ZERO_HASH` | `bytes([0] * 32)` |
| `FAR_FUTURE_SLOT` | `2**63` |
### Time parameters ### Time parameters
@ -196,9 +198,10 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
| `SLOT_DURATION` | `6` | seconds | 6 seconds | | `SLOT_DURATION` | `6` | seconds | 6 seconds |
| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**2` (= 4) | slots | 24 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `2**2` (= 4) | slots | 24 seconds |
| `EPOCH_LENGTH` | `2**6` (= 64) | slots | 6.4 minutes | | `EPOCH_LENGTH` | `2**6` (= 64) | slots | 6.4 minutes |
| `SEED_LOOKAHEAD` | `2**6` (= 64) | slots | 6.4 minutes |
| `ENTRY_EXIT_DELAY` | `2**8` (= 256) | slots | 25.6 minutes |
| `POW_RECEIPT_ROOT_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~1.7 hours | | `POW_RECEIPT_ROOT_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~1.7 hours |
| `SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` | `2**17` (= 131,072) | slots | ~9 days | | `SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD` | `2**17` (= 131,072) | slots | ~9 days |
| `COLLECTIVE_PENALTY_CALCULATION_PERIOD` | `2**20` (= 1,048,576) | slots | ~73 days |
| `ZERO_BALANCE_VALIDATOR_TTL` | `2**22` (= 4,194,304) | slots | ~291 days | | `ZERO_BALANCE_VALIDATOR_TTL` | `2**22` (= 4,194,304) | slots | ~291 days |
### Reward and penalty quotients ### Reward and penalty quotients
@ -213,15 +216,13 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
* 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 `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)`. * 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)`.
### Status codes ### Status flags
| Name | Value | | Name | Value |
| - | - | | - | - |
| `PENDING_ACTIVATION` | `0` | | `INITIATED_EXIT` | `2**0` (= 1) |
| `ACTIVE` | `1` | | `WITHDRAWABLE` | `2**1` (= 2) |
| `ACTIVE_PENDING_EXIT` | `2` |
| `EXITED_WITHOUT_PENALTY` | `3` |
| `EXITED_WITH_PENALTY` | `4` |
### Max operations per block ### Max operations per block
@ -521,12 +522,18 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
'randao_commitment': 'hash32', 'randao_commitment': 'hash32',
# Slots the proposer has skipped (i.e. layers of RANDAO expected) # Slots the proposer has skipped (i.e. layers of RANDAO expected)
'randao_layers': 'uint64', 'randao_layers': 'uint64',
# Status code # Slot when validator activated
'status': 'uint64', 'activation_slot': 'uint64',
# Slot when validator last changed status (or 0) # Slot when validator exited
'latest_status_change_slot': 'uint64', 'exit_slot': 'uint64',
# Exit counter when validator exited (or 0) # Slot when validator withdrew
'withdrawal_slot': 'uint64',
# Slot when validator was penalized
'penalized_slot': 'uint64',
# Exit counter when validator exited
'exit_count': 'uint64', 'exit_count': 'uint64',
# Status flags
'status_flags': 'uint64',
# Proof of custody commitment # Proof of custody commitment
'poc_commitment': 'hash32', 'poc_commitment': 'hash32',
# Slot the proof of custody seed was last changed # Slot the proof of custody seed was last changed
@ -618,6 +625,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted
latest_registry_delta_root: 'hash32', latest_registry_delta_root: 'hash32',
validator_index: 'uint24', validator_index: 'uint24',
pubkey: 'uint384', pubkey: 'uint384',
slot: 'uint64',
flag: 'uint64', flag: 'uint64',
} }
``` ```
@ -738,7 +746,7 @@ The beacon chain fork choice rule is a hybrid that combines justification and fi
def lmd_ghost(store, start): def lmd_ghost(store, start):
validators = start.state.validator_registry validators = start.state.validator_registry
active_validators = [validators[i] for i in active_validators = [validators[i] for i in
get_active_validator_indices(validators)] get_active_validator_indices(validators, start.state.slot)]
attestation_targets = [get_latest_attestation_target(store, validator) attestation_targets = [get_latest_attestation_target(store, validator)
for validator in active_validators] for validator in active_validators]
def get_vote_count(block): def get_vote_count(block):
@ -778,21 +786,21 @@ Note: We aim to migrate to a S[T/N]ARK-friendly hash function in a future Ethere
#### `is_active_validator` #### `is_active_validator`
```python ```python
def is_active_validator(validator: ValidatorRecord) -> bool: def is_active_validator(validator: ValidatorRecord, slot: int) -> bool:
""" """
Checks if ``validator`` is active. Checks if ``validator`` is active.
""" """
return validator.status in [ACTIVE, ACTIVE_PENDING_EXIT] return validator.activation_slot <= slot < validator.exit_slot
``` ```
#### `get_active_validator_indices` #### `get_active_validator_indices`
```python ```python
def get_active_validator_indices(validators: [ValidatorRecord]) -> List[int]: def get_active_validator_indices(validators: [ValidatorRecord], slot: int) -> List[int]:
""" """
Gets indices of active validators from ``validators``. Gets indices of active validators from ``validators``.
""" """
return [i for i, v in enumerate(validators) if is_active_validator(v)] return [i for i, v in enumerate(validators) if is_active_validator(v, slot)]
``` ```
#### `shuffle` #### `shuffle`
@ -862,16 +870,17 @@ def split(values: List[Any], split_count: int) -> List[Any]:
] ]
``` ```
#### `get_new_shuffling` #### `get_shuffling`
```python ```python
def get_new_shuffling(seed: Hash32, def get_shuffling(seed: Hash32,
validators: List[ValidatorRecord], validators: List[ValidatorRecord],
crosslinking_start_shard: int) -> List[List[ShardCommittee]]: crosslinking_start_shard: int,
slot: int) -> List[List[ShardCommittee]]:
""" """
Shuffles ``validators`` into shard committees using ``seed`` as entropy. Shuffles ``validators`` into shard committees using ``seed`` as entropy.
""" """
active_validator_indices = get_active_validator_indices(validators) active_validator_indices = get_active_validator_indices(validators, slot)
committees_per_slot = max( committees_per_slot = max(
1, 1,
@ -907,6 +916,8 @@ def get_new_shuffling(seed: Hash32,
return output return output
``` ```
**Invariant**: if `get_shuffling(seed, validators, shard, slot)` returns some value `x`, it should return the same value `x` for the same `seed` and `shard` and possible future modifications of `validators` forever in phase 0, and until the ~1 year deletion delay in phase 1 and in the future.
Here's a diagram of what is going on: Here's a diagram of what is going on:
![](http://vitalik.ca/files/ShuffleAndAssign.png?1) ![](http://vitalik.ca/files/ShuffleAndAssign.png?1)
@ -1008,6 +1019,7 @@ def get_effective_balance(state: State, index: int) -> int:
def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32, def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32,
validator_index: int, validator_index: int,
pubkey: int, pubkey: int,
slot: int,
flag: int) -> Hash32: flag: int) -> Hash32:
""" """
Compute the next root in the validator registry delta chain. Compute the next root in the validator registry delta chain.
@ -1017,6 +1029,7 @@ def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_
latest_registry_delta_root=current_validator_registry_delta_chain_tip, latest_registry_delta_root=current_validator_registry_delta_chain_tip,
validator_index=validator_index, validator_index=validator_index,
pubkey=pubkey, pubkey=pubkey,
slot=slot,
flag=flag, flag=flag,
) )
) )
@ -1196,7 +1209,7 @@ def get_initial_beacon_state(initial_validator_deposits: List[Deposit],
# Recent state # Recent state
latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, shard_block_root=ZERO_HASH) for _ in range(SHARD_COUNT)], latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, shard_block_root=ZERO_HASH) for _ in range(SHARD_COUNT)],
latest_block_roots=[ZERO_HASH for _ in range(LATEST_BLOCK_ROOTS_LENGTH)], latest_block_roots=[ZERO_HASH for _ in range(LATEST_BLOCK_ROOTS_LENGTH)],
latest_penalized_exit_balances=[], latest_penalized_exit_balances=[0 for _ in LATEST_PENALIZED_EXIT_LENGTH],
latest_attestations=[], latest_attestations=[],
batched_block_roots=[], batched_block_roots=[],
@ -1217,14 +1230,14 @@ def get_initial_beacon_state(initial_validator_deposits: List[Deposit],
poc_commitment=deposit.deposit_data.deposit_input.poc_commitment, poc_commitment=deposit.deposit_data.deposit_input.poc_commitment,
) )
if get_effective_balance(state, validator_index) == MAX_DEPOSIT * GWEI_PER_ETH: if get_effective_balance(state, validator_index) == MAX_DEPOSIT * GWEI_PER_ETH:
update_validator_status(state, validator_index, ACTIVE) activate_validator(state, validator_index)
# set initial committee shuffling # set initial committee shuffling
initial_shuffling = get_new_shuffling(ZERO_HASH, state.validator_registry, 0) initial_shuffling = get_shuffling(ZERO_HASH, state.validator_registry, 0, INITIAL_SLOT_NUMBER)
state.shard_committees_at_slots = initial_shuffling + initial_shuffling state.shard_committees_at_slots = initial_shuffling + initial_shuffling
# set initial persistent shuffling # set initial persistent shuffling
active_validator_indices = get_active_validator_indices(state.validator_registry) active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot)
state.persistent_committees = split(shuffle(active_validator_indices, ZERO_HASH), SHARD_COUNT) state.persistent_committees = split(shuffle(active_validator_indices, ZERO_HASH), SHARD_COUNT)
return state return state
@ -1238,8 +1251,8 @@ First, two helper functions:
def min_empty_validator_index(validators: List[ValidatorRecord], def min_empty_validator_index(validators: List[ValidatorRecord],
validator_balances: List[int], validator_balances: List[int],
current_slot: int) -> int: current_slot: int) -> int:
for i, (v, vbal) in enumerate(zip(validators, validator_balances)): for i, v in enumerate(validators):
if vbal == 0 and v.latest_status_change_slot + ZERO_BALANCE_VALIDATOR_TTL <= current_slot: if v.withdrawal_slot + ZERO_BALANCE_VALIDATOR_TTL <= current_slot:
return i return i
return None return None
``` ```
@ -1304,8 +1317,10 @@ def process_deposit(state: BeaconState,
withdrawal_credentials=withdrawal_credentials, withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment, randao_commitment=randao_commitment,
randao_layers=0, randao_layers=0,
status=PENDING_ACTIVATION, activation_slot=FAR_FUTURE_SLOT,
latest_status_change_slot=state.slot, exit_slot=FAR_FUTURE_SLOT,
withdrawal_slot=FAR_FUTURE_SLOT,
penalized_slot=FAR_FUTURE_SLOT,
exit_count=0, exit_count=0,
poc_commitment=poc_commitment, poc_commitment=poc_commitment,
last_poc_change_slot=0, last_poc_change_slot=0,
@ -1330,26 +1345,7 @@ def process_deposit(state: BeaconState,
return index return index
``` ```
### Routine for updating validator status ### Routines for updating validator status
```python
def update_validator_status(state: BeaconState,
index: int,
new_status: int) -> None:
"""
Update the validator status with the given ``index`` to ``new_status``.
Handle other general accounting related to this status update.
Note that this function mutates ``state``.
"""
if new_status == ACTIVE:
activate_validator(state, index)
if new_status == ACTIVE_PENDING_EXIT:
initiate_validator_exit(state, index)
if new_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY]:
exit_validator(state, index, new_status)
```
The following are helpers and should only be called via `update_validator_status`:
```python ```python
def activate_validator(state: BeaconState, def activate_validator(state: BeaconState,
@ -1359,15 +1355,15 @@ def activate_validator(state: BeaconState,
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
validator = state.validator_registry[index] validator = state.validator_registry[index]
if validator.status != PENDING_ACTIVATION: if validator.activation_slot <= state.slot:
return return
validator.status = ACTIVE validator.activation_slot = state.slot + ENTRY_EXIT_DELAY
validator.latest_status_change_slot = state.slot
state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip(
current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip,
validator_index=index, validator_index=index,
pubkey=validator.pubkey, pubkey=validator.pubkey,
slot=validator.activation_slot,
flag=ACTIVATION, flag=ACTIVATION,
) )
``` ```
@ -1380,40 +1376,22 @@ def initiate_validator_exit(state: BeaconState,
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
validator = state.validator_registry[index] validator = state.validator_registry[index]
if validator.status != ACTIVE: validator.status_flags |= INITIATED_EXIT
return
validator.status = ACTIVE_PENDING_EXIT
validator.latest_status_change_slot = state.slot
``` ```
```python ```python
def exit_validator(state: BeaconState, def exit_validator(state: BeaconState,
index: int, index: int) -> None:
new_status: int) -> None:
""" """
Exit the validator with the given ``index``. Exit the validator with the given ``index``.
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
validator = state.validator_registry[index] validator = state.validator_registry[index]
prev_status = validator.status
if prev_status == EXITED_WITH_PENALTY: if validator.exit_slot < state.slot + ENTRY_EXIT_DELAY:
return return
validator.status = new_status validator.exit_slot = state.slot + ENTRY_EXIT_DELAY
validator.latest_status_change_slot = state.slot
if new_status == EXITED_WITH_PENALTY:
state.latest_penalized_exit_balances[state.slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD] += get_effective_balance(state, index)
whistleblower_index = get_beacon_proposer_index(state, state.slot)
whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT
state.validator_balances[whistleblower_index] += whistleblower_reward
state.validator_balances[index] -= whistleblower_reward
if prev_status == EXITED_WITHOUT_PENALTY:
return
# The following updates only occur if not previous exited # The following updates only occur if not previous exited
state.validator_registry_exit_count += 1 state.validator_registry_exit_count += 1
@ -1422,8 +1400,10 @@ def exit_validator(state: BeaconState,
current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip,
validator_index=index, validator_index=index,
pubkey=validator.pubkey, pubkey=validator.pubkey,
slot=validator.exit_slot,
flag=EXIT, flag=EXIT,
) )
state.validator_registry_exit_count += 1
# Remove validator from persistent committees # Remove validator from persistent committees
for committee in state.persistent_committees: for committee in state.persistent_committees:
@ -1433,6 +1413,27 @@ def exit_validator(state: BeaconState,
break break
``` ```
```python
def penalize_validator(state: BeaconState,
index: int) -> None:
exit_validator(state, index)
state.latest_penalized_exit_balances[(state.slot // EPOCH_LENGTH) % LATEST_PENALIZED_EXIT_LENGTH] += get_effective_balance(state, index)
whistleblower_index = get_beacon_proposer_index(state, state.slot)
whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT
state.validator_balances[whistleblower_index] += whistleblower_reward
state.validator_balances[index] -= whistleblower_reward
state.validator_registry[index].penalized_slot = state.slot
```
```python
def set_validator_withdrawable(state: BeaconState,
index: int) -> None:
state.validator_registry[index].status_flags |= WITHDRAWABLE
```
## Per-slot processing ## Per-slot processing
Below are the processing steps that happen at every slot. Below are the processing steps that happen at every slot.
@ -1492,7 +1493,7 @@ For each `proposer_slashing` in `block.body.proposer_slashings`:
* Verify that `proposer.status != EXITED_WITH_PENALTY`. * Verify that `proposer.status != EXITED_WITH_PENALTY`.
* Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_1), signature=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_1.slot, DOMAIN_PROPOSAL))`. * Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_1), signature=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_1.slot, DOMAIN_PROPOSAL))`.
* Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_2), signature=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_2.slot, DOMAIN_PROPOSAL))`. * Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_2), signature=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_2.slot, DOMAIN_PROPOSAL))`.
* Run `update_validator_status(state, proposer_slashing.proposer_index, new_status=EXITED_WITH_PENALTY)`. * Run `penalize_validator(state, proposer_slashing.proposer_index)`.
#### Casper slashings #### Casper slashings
@ -1509,7 +1510,7 @@ For each `casper_slashing` in `block.body.casper_slashings`:
* Verify that `is_double_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)` or `is_surround_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)`. * Verify that `is_double_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)` or `is_surround_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)`.
* Verify that `verify_slashable_vote_data(state, slashable_vote_data_1)`. * Verify that `verify_slashable_vote_data(state, slashable_vote_data_1)`.
* Verify that `verify_slashable_vote_data(state, slashable_vote_data_2)`. * Verify that `verify_slashable_vote_data(state, slashable_vote_data_2)`.
* For each [validator](#dfn-validator) index `i` in `intersection`, if `state.validator_registry[i].status` does not equal `EXITED_WITH_PENALTY`, then run `update_validator_status(state, i, new_status=EXITED_WITH_PENALTY)` * For each [validator](#dfn-validator) index `i` in `intersection`, if `state.validator_registry[i].status` does not equal `EXITED_WITH_PENALTY`, then run `penalize_validator(state, i)`.
#### Attestations #### Attestations
@ -1573,11 +1574,10 @@ Verify that `len(block.body.exits) <= MAX_EXITS`.
For each `exit` in `block.body.exits`: For each `exit` in `block.body.exits`:
* Let `validator = state.validator_registry[exit.validator_index]`. * Let `validator = state.validator_registry[exit.validator_index]`.
* Verify that `validator.status == ACTIVE`. * Verify that `validator.exit_slot > state.slot + ENTRY_EXIT_DELAY`.
* Verify that `state.slot >= exit.slot`. * Verify that `state.slot >= exit.slot`.
* Verify that `state.slot >= validator.latest_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD`.
* Verify that `bls_verify(pubkey=validator.pubkey, message=ZERO_HASH, signature=exit.signature, domain=get_domain(state.fork_data, exit.slot, DOMAIN_EXIT))`. * Verify that `bls_verify(pubkey=validator.pubkey, message=ZERO_HASH, signature=exit.signature, domain=get_domain(state.fork_data, exit.slot, DOMAIN_EXIT))`.
* Run `update_validator_status(state, validator_index, new_status=ACTIVE_PENDING_EXIT)`. * Run `initiate_validator_exit(state, validator_index)`.
#### Miscellaneous #### Miscellaneous
@ -1591,7 +1591,7 @@ The steps below happen when `state.slot % EPOCH_LENGTH == 0`.
All [validators](#dfn-validator): All [validators](#dfn-validator):
* Let `active_validator_indices = get_active_validator_indices(state.validator_registry)`. * Let `active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot)`.
* Let `total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices])`. * Let `total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices])`.
[Validators](#dfn-Validator) attesting during the current epoch: [Validators](#dfn-Validator) attesting during the current epoch:
@ -1718,7 +1718,7 @@ def process_ejections(state: BeaconState) -> None:
""" """
for index in active_validator_indices(state.validator_registry): for index in active_validator_indices(state.validator_registry):
if state.validator_balances[index] < EJECTION_BALANCE: if state.validator_balances[index] < EJECTION_BALANCE:
update_validator_status(state, index, new_status=EXITED_WITHOUT_PENALTY) exit_validator(state, index)
``` ```
### Validator registry ### Validator registry
@ -1737,7 +1737,7 @@ def update_validator_registry(state: BeaconState) -> None:
Note that this function mutates ``state``. Note that this function mutates ``state``.
""" """
# The active validators # The active validators
active_validator_indices = get_active_validator_indices(state.validator_registry) active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot)
# The total effective balance of active validators # The total effective balance of active validators
total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices]) total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices])
@ -1750,65 +1750,89 @@ def update_validator_registry(state: BeaconState) -> None:
# Activate validators within the allowable balance churn # Activate validators within the allowable balance churn
balance_churn = 0 balance_churn = 0
for index, validator in enumerate(state.validator_registry): for index, validator in enumerate(state.validator_registry):
if validator.status == PENDING_ACTIVATION and state.validator_balances[index] >= MAX_DEPOSIT * GWEI_PER_ETH: if validator.activation_slot > state.slot and state.validator_balances[index] >= MAX_DEPOSIT * GWEI_PER_ETH:
# Check the balance churn would be within the allowance # Check the balance churn would be within the allowance
balance_churn += get_effective_balance(state, index) balance_churn += get_effective_balance(state, index)
if balance_churn > max_balance_churn: if balance_churn > max_balance_churn:
break break
# Activate validator # Activate validator
update_validator_status(state, index, new_status=ACTIVE) activate_validator(state, index)
# Exit validators within the allowable balance churn # Exit validators within the allowable balance churn
balance_churn = 0 balance_churn = 0
for index, validator in enumerate(state.validator_registry): for index, validator in enumerate(state.validator_registry):
if validator.status == ACTIVE_PENDING_EXIT: if validator.exit_slot > state.slot + ENTRY_EXIT_DELAY and validator.status_flags & INITIATED_EXIT:
# Check the balance churn would be within the allowance # Check the balance churn would be within the allowance
balance_churn += get_effective_balance(state, index) balance_churn += get_effective_balance(state, index)
if balance_churn > max_balance_churn: if balance_churn > max_balance_churn:
break break
# Exit validator # Exit validator
update_validator_status(state, index, new_status=EXITED_WITHOUT_PENALTY) exit_validator(state, index)
```
Regardless of whether the above conditions are satisfied, run the following:
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods ```python
period_index = current_slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD def process_penalties_and_exits(state: BeaconState) -> None:
total_penalties = (
(latest_penalized_exit_balances[period_index]) +
(latest_penalized_exit_balances[period_index - 1] if period_index >= 1 else 0) +
(latest_penalized_exit_balances[period_index - 2] if period_index >= 2 else 0)
)
# Calculate penalties for slashed validators # The active validators
def to_penalize(index): active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot)
return state.validator_registry[index].status == EXITED_WITH_PENALTY # The total effective balance of active validators
validators_to_penalize = filter(to_penalize, range(len(validator_registry))) total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices])
for index in validators_to_penalize:
state.validator_balances[index] -= get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance
return validator_registry, latest_penalized_exit_balances, validator_registry_delta_chain_tip for i, validator in enumerate(validators):
if (state.slot // EPOCH_LENGTH) - (validator.penalized_slot // EPOCH_LENGTH) == LATEST_PENALIZED_EXIT_LENGTH // 2:
e = (state.slot // EPOCH_LENGTH) % LATEST_PENALIZED_EXIT_LENGTH
total_at_start = state.latest_penalized_exit_balances[(e+1)%LATEST_PENALIZED_EXIT_LENGTH]
total_at_end = state.latest_penalized_exit_balances[e]
total_penalties = total_at_end - total_at_start
penalty = get_effective_balance(state, i) * min(total_penalties * 3, total_balance) // total_balance
state.validator_balances[index] -= penalty
def eligible(index):
validator = state.validator_registry[index]
if validator.penalized_slot <= state.slot:
PENALIZED_WITHDRAWAL_TIME = LATEST_PENALIZED_EXIT_LENGTH * EPOCH_LENGTH // 2
return state.slot >= validator.penalized_slot + PENALIZED_WITHDRAWAL_TIME
else:
return state.slot >= validator.exit_slot + MIN_VALIDATOR_WITHDRAWAL_TIME
eligible_indices = filter(eligible, all_indices)
sorted_indices = sorted(eligible_indices, filter=lambda index: state.validator_registry[index].exit_count)
withdrawn_so_far = 0
for index in sorted_indices:
validator = state.validator_registry[index]
if validator.status == EXITED_WITH_PENALTY:
# TODO: calculate and apply penalties for slashed validators
penalty = 1
state.validator_balances[index] -= penalty
set_validator_withdrawable(state, index)
withdrawn_so_far += 1
if withdrawn_so_far >= MAX_WITHDRAWALS_PER_EPOCH:
break
``` ```
Also perform the following updates: Also perform the following updates:
* Set `state.validator_registry_latest_change_slot = state.slot`. * Set `state.validator_registry_latest_change_slot = state.slot`.
* Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`. * Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`.
* Set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.latest_randao_mixes[(state.slot - EPOCH_LENGTH) % LATEST_RANDAO_MIXES_LENGTH], state.validator_registry, next_start_shard)` where `next_start_shard = (state.shard_committees_at_slots[-1][-1].shard + 1) % SHARD_COUNT`. * Set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_shuffling(state.latest_randao_mixes[(state.slot - EPOCH_LENGTH) % LATEST_RANDAO_MIXES_LENGTH], state.validator_registry, next_start_shard, state.slot)` where `next_start_shard = (state.shard_committees_at_slots[-1][-1].shard + 1) % SHARD_COUNT`.
If a validator registry update does _not_ happen do the following: If a validator registry update does _not_ happen do the following:
* Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`. * Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`.
* Let `epochs_since_last_registry_change = (state.slot - state.validator_registry_latest_change_slot) // EPOCH_LENGTH`. * Let `epochs_since_last_registry_change = (state.slot - state.validator_registry_latest_change_slot) // EPOCH_LENGTH`.
* Let `start_shard = state.shard_committees_at_slots[0][0].shard`. * Let `start_shard = state.shard_committees_at_slots[0][0].shard`.
* If `epochs_since_last_registry_change` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.latest_randao_mixes[(state.slot - EPOCH_LENGTH) % LATEST_RANDAO_MIXES_LENGTH], state.validator_registry, start_shard)`. Note that `start_shard` is not changed from the last epoch. * If `epochs_since_last_registry_change` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_shuffling(state.latest_randao_mixes[(state.slot - EPOCH_LENGTH) % LATEST_RANDAO_MIXES_LENGTH], state.validator_registry, start_shard, state.slot)`. Note that `start_shard` is not changed from the last epoch.
### Proposer reshuffling ### Proposer reshuffling
Run the following code to update the shard proposer set: Run the following code to update the shard proposer set:
```python ```python
active_validator_indices = get_active_validator_indices(state.validator_registry) active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot)
num_validators_to_reshuffle = len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD num_validators_to_reshuffle = len(active_validator_indices) // SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD
for i in range(num_validators_to_reshuffle): for i in range(num_validators_to_reshuffle):
# Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling # Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling
@ -1832,6 +1856,7 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com
### Final updates ### Final updates
* Let `e = state.slot // EPOCH_LENGTH`. Set `state.latest_penalized_exit_balances[(e+1) % LATEST_PENALIZED_EXIT_LENGTH] = state.latest_penalized_exit_balances[e % LATEST_PENALIZED_EXIT_LENGTH]`
* Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < state.slot - EPOCH_LENGTH`. * Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < state.slot - EPOCH_LENGTH`.
## State root processing ## State root processing