From 6f058c2756492a615187babae7c7460c4b0e93e0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 10 Dec 2018 15:16:06 -0600 Subject: [PATCH 1/9] begin validator status cleanup --- specs/core/0_beacon-chain.md | 294 ++++++++++++++++------------------- 1 file changed, 138 insertions(+), 156 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index cad7507e9..a59015390 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1028,30 +1028,6 @@ A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the follow def on_startup(initial_validator_entries: List[Any], genesis_time: int, processed_pow_receipt_root: Hash32) -> BeaconState: - # Activate validators - initial_validator_registry = [] - for pubkey, deposit, proof_of_possession, withdrawal_credentials, randao_commitment in initial_validator_entries: - initial_validator_registry, _ = get_new_validators( - current_validators=initial_validator_registry, - fork_data=ForkData( - pre_fork_version=INITIAL_FORK_VERSION, - post_fork_version=INITIAL_FORK_VERSION, - fork_slot=INITIAL_SLOT_NUMBER, - ), - pubkey=pubkey, - deposit=deposit, - proof_of_possession=proof_of_possession, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment, - status=ACTIVE, - current_slot=INITIAL_SLOT_NUMBER, - ) - - # Setup state - initial_shuffling = get_new_shuffling(ZERO_HASH, initial_validator_registry, 0) - active_validator_indices = get_active_validator_indices(initial_validator_registry) - initial_persistent_committees = split(shuffle(active_validator_indices, ZERO_HASH), SHARD_COUNT) - state = BeaconState( # Misc slot=INITIAL_SLOT_NUMBER, @@ -1071,8 +1047,8 @@ def on_startup(initial_validator_entries: List[Any], # Randomness and committees randao_mix=ZERO_HASH, next_seed=ZERO_HASH, - shard_committees_at_slots=initial_shuffling + initial_shuffling, - persistent_committees=initial_persistent_committees, + shard_committees_at_slots=[], + persistent_committees=[], persistent_committee_reassignments=[], # Finality @@ -1092,12 +1068,33 @@ def on_startup(initial_validator_entries: List[Any], candidate_pow_receipt_roots=[], ) + # handle initial deposits and activations + for pubkey, deposit, proof_of_possession, withdrawal_credentials, randao_commitment in initial_validator_entries: + validator_index = process_deposit( + state=state, + pubkey=pubkey, + deposit=deposit, + proof_of_possession=proof_of_possession, + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment + ) + if state.validator_registry[index].balance >= MAX_DEPOSIT: + update_validator_status(index, state, ACTIVE) + + # set initial committee shuffling + initial_shuffling = get_new_shuffling(ZERO_HASH, initial_validator_registry, 0) + state.shard_committees_at_slots = initial_shuffling + initial_shuffling + + # set initial persistent shuffling + active_validator_indices = get_active_validator_indices(state.validator_registry) + state.persistent_committees = split(shuffle(active_validator_indices, ZERO_HASH), SHARD_COUNT) + return state ``` -### Routine for activating a validator +### Routine for processing deposits -This routine should be run for every [validator](#dfn-validator) that is activated as part of a log created on Ethereum 1.0 [TODO: explain where to check for these logs]. The status of the [validators](#dfn-validator) added after genesis is `PENDING_ACTIVATION`. These logs should be processed in the order in which they are emitted by Ethereum 1.0. +These logs should be processed in the order in which they are emitted by Ethereum 1.0. First, some helper functions: @@ -1114,60 +1111,9 @@ def get_fork_version(fork_data: ForkData, return fork_data.pre_fork_version else: return fork_data.post_fork_version - -def get_new_validators(validators: List[ValidatorRecord], - fork_data: ForkData, - pubkey: int, - deposit: int, - proof_of_possession: bytes, - withdrawal_credentials: Hash32, - randao_commitment: Hash32, - status: int, - current_slot: int) -> Tuple[List[ValidatorRecord], int]: - assert BLSVerify( - pub=pubkey, - msg=hash(bytes32(pubkey) + withdrawal_credentials + randao_commitment), - sig=proof_of_possession, - domain=get_domain( - fork_data, - current_slot, - DOMAIN_DEPOSIT - ) - ) - validators_copy = copy.deepcopy(validators) - validator_pubkeys = [v.pubkey for v in validators_copy] - - if pubkey not in validator_pubkeys: - # Add new validator - validator = ValidatorRecord( - pubkey=pubkey, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment, - randao_skips=0, - balance=deposit, - status=status, - latest_status_change_slot=current_slot, - exit_count=0 - ) - - index = min_empty_validator_index(validators_copy) - if index is None: - validators_copy.append(validator) - index = len(validators_copy) - 1 - else: - validators_copy[index] = validator - else: - # Increase balance by deposit - index = validator_pubkeys.index(pubkey) - validator = validators_copy[index] - assert validator.withdrawal_credentials == withdrawal_credentials - - validator.balance += deposit - - return validators_copy, index ``` -`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS12-381 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). +`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS12-381 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). Now, to add a [validator](#dfn-validator) or top up an existing [validator](#dfn-validator)'s balance: ```python @@ -1182,41 +1128,128 @@ def process_deposit(state: BeaconState, Process a deposit from Ethereum 1.0. Note that this function mutates ``state``. """ - state.validator_registry, index = get_new_validators( - current_validators=state.validator_registry, - fork_data=ForkData( - pre_fork_version=state.fork_data.pre_fork_version, - post_fork_version=state.fork_data.post_fork_version, - fork_slot=state.fork_data.fork_slot, - ), - pubkey=pubkey, - deposit=deposit, - proof_of_possession=proof_of_possession, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment, - status=status, - current_slot=state.slot, + assert BLSVerify( + pub=pubkey, + msg=hash(bytes32(pubkey) + withdrawal_credentials + randao_commitment), + sig=proof_of_possession, + domain=get_domain( + state.fork_data, + state.slot, + DOMAIN_DEPOSIT + ) ) + validator_pubkeys = [v.pubkey for v in state.validator_registry] + + if pubkey not in validator_pubkeys: + # Add new validator + validator = ValidatorRecord( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + randao_commitment=randao_commitment, + randao_skips=0, + balance=deposit, + status=PENDING_ACTIVATION, + latest_status_change_slot=state.slot, + exit_count=0 + ) + + index = min_empty_validator_index(validators_copy) + if index is None: + state.validator_registry.append(validator) + index = len(validators_copy) - 1 + else: + state.validator_registry[index] = validator + else: + # Increase balance by deposit + index = validator_pubkeys.index(pubkey) + validator = state.validator_registry[index] + assert validator.withdrawal_credentials == withdrawal_credentials + + validator.balance += deposit return index ``` -### Routine for exiting a validator +### Routines for updating validator status + +```python +def update_validator_status(index: int, + state: BeaconState, + new_status: int) -> None: + """ + Update the validator status with the given ``index`` to ``new_status``. + Note that this function mutates ``state``. + """ + if new_status == ACTIVE: + activate_validator(index, state) + if new_status == ACTIVE_PENDING_EXIT: + initiate_validator_exit(index, state) + if new_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY]: + exit_validator(index, state, new_status) +``` + +```python +def activate_validator(index: int, + state: BeaconState) -> None: + """ + Activate the validator with the given ``index``. + Note that this function mutates ``state``. + """ + if validator.status != PENDING_ACTIVATION: + return + + validator = state.validator_registry[index] + validator.status = ACTIVE + validator.latest_status_change_slot = state.slot + state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( + validator_registry_delta_chain_tip=validator_registry_delta_chain_tip, + index=i, + pubkey=validator.pubkey, + flag=ACTIVATION, + ) +``` + +```python +def initiate_validator_exit(index: int, + state: BeaconState) -> None: + validator = state.validator_registry[index] + validator.status = ACTIVE_PENDING_EXIT + validator.latest_status_change_slot = state.slot +``` ```python def exit_validator(index: int, state: BeaconState, - new_status: bool) -> None: + new_status: int) -> None: """ Exit the validator with the given ``index``. Note that this function mutates ``state``. """ - state.validator_registry_exit_count += 1 - validator = state.validator_registry[index] + prev_status = validator.status validator.status = new_status 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(validator) + + whistleblower = state.validator_registry[get_beacon_proposer_index(state, state.slot)] + whistleblower_reward = validator.balance // WHISTLEBLOWER_REWARD_QUOTIENT + whistleblower.balance += whistleblower_reward + validator.balance -= whistleblower_reward + + if prev_status != ACTIVE: + return + + # The following updates only occur if previously ACTIVE + state.validator_registry_exit_count += 1 validator.exit_count = state.validator_registry_exit_count + state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( + validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, + index=index, + pubkey=validator.pubkey, + flag=EXIT + ) # Remove validator from persistent committees for committee in state.persistent_committees: @@ -1224,21 +1257,6 @@ def exit_validator(index: int, if validator_index == index: committee.pop(i) break - - if new_status == EXITED_WITH_PENALTY: - state.latest_penalized_exit_balances[state.slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD] += get_effective_balance(validator) - - whistleblower = state.validator_registry[get_beacon_proposer_index(state, state.slot)] - whistleblower_reward = validator.balance // WHISTLEBLOWER_REWARD_QUOTIENT - whistleblower.balance += whistleblower_reward - validator.balance -= whistleblower_reward - - state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( - validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, - index=index, - pubkey=validator.pubkey, - flag=EXIT, - ) ``` ## Per-slot processing @@ -1361,8 +1379,7 @@ process_deposit( deposit=deposit.deposit_data.value, proof_of_possession=deposit.deposit_data.deposit_parameters.proof_of_possession, withdrawal_credentials=deposit.deposit_data.deposit_parameters.withdrawal_credentials, - randao_commitment=deposit.deposit_data.deposit_parameters.randao_commitment, - status=PENDING_ACTIVATION + randao_commitment=deposit.deposit_data.deposit_parameters.randao_commitment ) ``` @@ -1511,30 +1528,8 @@ def update_validator_registry(state: BeaconState) -> None: Update validator registry. Note that this function mutates ``state``. """ - state.validator_registry, state.latest_penalized_exit_balances, state.validator_registry_delta_chain_tip = get_updated_validator_registry( - state.validator_registry, - state.latest_penalized_exit_balances, - state.validator_registry_delta_chain_tip, - state.slot - ) -``` - -which utilizes the following helper - -```python -def get_updated_validator_registry(validator_registry: List[ValidatorRecord], - latest_penalized_exit_balances: List[int], - validator_registry_delta_chain_tip: int, - current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]: - """ - Returns the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``. - """ - # make copies to prevent mutating inputs - validator_registry = copy.deepcopy(state.validator_registry) - latest_penalized_exit_balances = copy.deepcopy(latest_penalized_exit_balances) - # The active validators - active_validator_indices = get_active_validator_indices(validator_registry) + active_validator_indices = get_active_validator_indices(state.validator_registry) # The total effective balance of active validators total_balance = sum([get_effective_balance(v) for i, v in enumerate(validator_registry) if i in active_validator_indices]) @@ -1546,7 +1541,7 @@ def get_updated_validator_registry(validator_registry: List[ValidatorRecord], # Activate validators within the allowable balance churn balance_churn = 0 - for i, validator in enumerate(validator_registry): + for i, validator in enumerate(state.validator_registry): if validator.status == PENDING_ACTIVATION and validator.balance >= MAX_DEPOSIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(validator) @@ -1554,18 +1549,11 @@ def get_updated_validator_registry(validator_registry: List[ValidatorRecord], break # Activate validator - validator.status = ACTIVE - validator.latest_status_change_slot = current_slot - validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( - validator_registry_delta_chain_tip=validator_registry_delta_chain_tip, - index=i, - pubkey=validator.pubkey, - flag=ACTIVATION, - ) + update_validator_status(i, state, new_status=ACTIVE) # Exit validators within the allowable balance churn balance_churn = 0 - for i, validator in enumerate(validator_registry): + for i, validator in enumerate(state.validator_registry): if validator.status == ACTIVE_PENDING_EXIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(validator) @@ -1573,14 +1561,8 @@ def get_updated_validator_registry(validator_registry: List[ValidatorRecord], break # Exit validator - validator.status = EXITED_WITHOUT_PENALTY - validator.latest_status_change_slot = current_slot - validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( - validator_registry_delta_chain_tip=validator_registry_delta_chain_tip, - index=i, - pubkey=validator.pubkey, - flag=EXIT, - ) + update_validator_status(i, state, new_status=EXITED_WITHOUT_PENALTY) + # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods period_index = current_slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD From 9cff4ae653b4f9a6925a8c45eb32c6c36a038b1a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 10 Dec 2018 15:56:22 -0600 Subject: [PATCH 2/9] fix exit conditions --- 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 a59015390..a1ca312f2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1238,10 +1238,10 @@ def exit_validator(index: int, whistleblower.balance += whistleblower_reward validator.balance -= whistleblower_reward - if prev_status != ACTIVE: + if prev_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY] return - # The following updates only occur if previously ACTIVE + # The following updates only occur if not previous exited state.validator_registry_exit_count += 1 validator.exit_count = state.validator_registry_exit_count state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( From 03901ffe65057f007067ac45cba81693df15044b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 10 Dec 2018 16:14:08 -0600 Subject: [PATCH 3/9] cleanups --- specs/core/0_beacon-chain.md | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a1ca312f2..5a450ec3b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -71,9 +71,11 @@ - [`bytes1`, `bytes2`, ...](#bytes1-bytes2-) - [`get_effective_balance`](#get_effective_balance) - [`get_new_validator_registry_delta_chain_tip`](#get_new_validator_registry_delta_chain_tip) + - [`get_fork_version`](#get_fork_version) - [`get_domain`](#get_domain) - [`verify_casper_votes`](#verify_casper_votes) - [`integer_squareroot`](#integer_squareroot) + - [`BLSVerify`](#blsverify) - [On startup](#on-startup) - [Routine for activating a validator](#routine-for-activating-a-validator) - [Routine for exiting a validator](#routine-for-exiting-a-validator) @@ -959,6 +961,17 @@ def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_ ) ``` +#### `get_fork_version` + +```python +def get_fork_version(fork_data: ForkData, + slot: int) -> int: + if slot < fork_data.fork_slot: + return fork_data.pre_fork_version + else: + return fork_data.post_fork_version +``` + #### `get_domain` ```python @@ -998,6 +1011,10 @@ def integer_squareroot(n: int) -> int: return x ``` +#### `BLSVerify` + +`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS12-381 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). + ### On startup A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the following values. Other validity rules (e.g. requiring a signature) do not apply. @@ -1094,9 +1111,7 @@ def on_startup(initial_validator_entries: List[Any], ### Routine for processing deposits -These logs should be processed in the order in which they are emitted by Ethereum 1.0. - -First, some helper functions: +First, a helper function: ```python def min_empty_validator_index(validators: List[ValidatorRecord], current_slot: int) -> int: @@ -1104,17 +1119,9 @@ def min_empty_validator_index(validators: List[ValidatorRecord], current_slot: i if v.balance == 0 and v.latest_status_change_slot + ZERO_BALANCE_VALIDATOR_TTL <= current_slot: return i return None - -def get_fork_version(fork_data: ForkData, - slot: int) -> int: - if slot < fork_data.fork_slot: - return fork_data.pre_fork_version - else: - return fork_data.post_fork_version ``` -`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS12-381 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). -Now, to add a [validator](#dfn-validator) or top up an existing [validator](#dfn-validator)'s balance: +Now, to add a [validator](#dfn-validator) or top up an existing [validator](#dfn-validator)'s balance by some `deposit` amount: ```python def process_deposit(state: BeaconState, @@ -1353,6 +1360,8 @@ For each `attestation` in `block.body.attestations`: Verify that `len(block.body.deposits) <= MAX_DEPOSITS`. +[TODO: add logic to ensure that deposits from 1.0 chain are processed in order:] + For each `deposit` in `block.body.deposits`: * Let `serialized_deposit_data` be the serialized form of `deposit.deposit_data`. It should be the `DepositParameters` followed by 8 bytes for `deposit_data.value` and 8 bytes for `deposit_data.timestamp`. That is, it should match `deposit_data` in the [Ethereum 1.0 deposit contract](#ethereum-10-chain-deposit-contract) of which the hash was placed into the Merkle tree. From 0c281c81139c5e03d34d3690faf229d762a6ecb4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 10 Dec 2018 16:17:42 -0600 Subject: [PATCH 4/9] local var i -> index --- 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 5a450ec3b..1c6e3b07f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1210,7 +1210,7 @@ def activate_validator(index: int, validator.latest_status_change_slot = state.slot state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( validator_registry_delta_chain_tip=validator_registry_delta_chain_tip, - index=i, + index=index, pubkey=validator.pubkey, flag=ACTIVATION, ) @@ -1360,7 +1360,7 @@ For each `attestation` in `block.body.attestations`: Verify that `len(block.body.deposits) <= MAX_DEPOSITS`. -[TODO: add logic to ensure that deposits from 1.0 chain are processed in order:] +[TODO: add logic to ensure that deposits from 1.0 chain are processed in order] For each `deposit` in `block.body.deposits`: From 70765a205e7df58599f5aba94feffacfa4e61f9a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 10 Dec 2018 16:30:25 -0600 Subject: [PATCH 5/9] fix toc --- specs/core/0_beacon-chain.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1c6e3b07f..0866a2e1b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -77,8 +77,8 @@ - [`integer_squareroot`](#integer_squareroot) - [`BLSVerify`](#blsverify) - [On startup](#on-startup) - - [Routine for activating a validator](#routine-for-activating-a-validator) - - [Routine for exiting a validator](#routine-for-exiting-a-validator) + - [Routine for processing deposits](#routine-for-processing-deposits) + - [Routine for updating validator status](#routine-for-updating-validator-status) - [Per-slot processing](#per-slot-processing) - [Proposer signature](#proposer-signature) - [RANDAO](#randao) @@ -1177,7 +1177,7 @@ def process_deposit(state: BeaconState, return index ``` -### Routines for updating validator status +### Routine for updating validator status ```python def update_validator_status(index: int, @@ -1550,7 +1550,7 @@ def update_validator_registry(state: BeaconState) -> None: # Activate validators within the allowable balance churn balance_churn = 0 - for i, validator in enumerate(state.validator_registry): + for index, validator in enumerate(state.validator_registry): if validator.status == PENDING_ACTIVATION and validator.balance >= MAX_DEPOSIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(validator) @@ -1558,11 +1558,11 @@ def update_validator_registry(state: BeaconState) -> None: break # Activate validator - update_validator_status(i, state, new_status=ACTIVE) + update_validator_status(index, state, new_status=ACTIVE) # Exit validators within the allowable balance churn balance_churn = 0 - for i, validator in enumerate(state.validator_registry): + for index, validator in enumerate(state.validator_registry): if validator.status == ACTIVE_PENDING_EXIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(validator) @@ -1570,7 +1570,7 @@ def update_validator_registry(state: BeaconState) -> None: break # Exit validator - update_validator_status(i, state, new_status=EXITED_WITHOUT_PENALTY) + update_validator_status(index, state, new_status=EXITED_WITHOUT_PENALTY) # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods From e77bf04711e7194acd151116ba31f71d1fe54086 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 10 Dec 2018 16:42:28 -0600 Subject: [PATCH 6/9] remove direct calls to exit_validator --- specs/core/0_beacon-chain.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 0866a2e1b..f38545dda 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1195,6 +1195,8 @@ def update_validator_status(index: int, exit_validator(index, state, new_status) ``` +The following are helpers and should only be called via `update_validator_status`: + ```python def activate_validator(index: int, state: BeaconState) -> None: @@ -1321,7 +1323,7 @@ For each `proposer_slashing` in `block.body.proposer_slashings`: * Verify that `proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard`. * Verify that `proposer_slashing.proposal_data_1.block_hash != proposer_slashing.proposal_data_2.block_hash`. * Verify that `proposer.status != EXITED_WITH_PENALTY`. -* Run `exit_validator(proposer_slashing.proposer_index, state, new_status=EXITED_WITH_PENALTY)`. +* Run `update_validator_status(proposer_slashing.proposer_index, state, new_status=EXITED_WITH_PENALTY)`. #### Casper slashings @@ -1336,7 +1338,7 @@ For each `casper_slashing` in `block.body.casper_slashings`: * Let `intersection = [x for x in indices(casper_slashing.votes_1) if x in indices(casper_slashing.votes_2)]`. * Verify that `len(intersection) >= 1`. * Verify that `casper_slashing.votes_1.data.justified_slot + 1 < casper_slashing.votes_2.data.justified_slot + 1 == casper_slashing.votes_2.data.slot < casper_slashing.votes_1.data.slot` or `casper_slashing.votes_1.data.slot == casper_slashing.votes_2.data.slot`. -* For each [validator](#dfn-validator) index `i` in `intersection`, if `state.validator_registry[i].status` does not equal `EXITED_WITH_PENALTY`, then run `exit_validator(i, state, 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 `update_validator_status(i, state, new_status=EXITED_WITH_PENALTY)` #### Attestations @@ -1403,7 +1405,7 @@ For each `exit` in `block.body.exits`: * Verify that `validator.status == ACTIVE`. * Verify that `state.slot >= exit.slot`. * Verify that `state.slot >= validator.latest_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD`. -* Run `exit_validator(validator_index, state, new_status=ACTIVE_PENDING_EXIT)`. +* Run `update_validator_status(validator_index, state, new_status=ACTIVE_PENDING_EXIT)`. ### Ejections @@ -1415,9 +1417,9 @@ def process_ejections(state: BeaconState) -> None: Iterate through the validator registry and eject active validators with balance below ``EJECTION_BALANCE``. """ - for i, v in enumerate(state.validator_registry): - if is_active_validator(v) and v.balance < EJECTION_BALANCE: - exit_validator(i, state, new_status=EXITED_WITHOUT_PENALTY) + for index, validator in enumerate(state.validator_registry): + if is_active_validator(validor) and validator.balance < EJECTION_BALANCE: + update_validator_status(index, state, new_status=EXITED_WITHOUT_PENALTY) ``` ## Per-epoch processing From 1e3f518f021d53cdb1a17d402b7c25bd9ba1daec Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 11 Dec 2018 09:49:50 -0600 Subject: [PATCH 7/9] pr feedback --- specs/core/0_beacon-chain.md | 50 ++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 9acc9625c..7f6edd12b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1096,7 +1096,7 @@ def on_startup(initial_validator_entries: List[Any], randao_commitment=randao_commitment ) if state.validator_registry[index].balance >= MAX_DEPOSIT: - update_validator_status(index, state, ACTIVE) + update_validator_status(state, index, ACTIVE) # set initial committee shuffling initial_shuffling = get_new_shuffling(ZERO_HASH, initial_validator_registry, 0) @@ -1180,26 +1180,27 @@ def process_deposit(state: BeaconState, ### Routine for updating validator status ```python -def update_validator_status(index: int, - state: BeaconState, +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(index, state) + activate_validator(state, index) if new_status == ACTIVE_PENDING_EXIT: - initiate_validator_exit(index, state) + initiate_validator_exit(state, index) if new_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY]: - exit_validator(index, state, new_status) + exit_validator(state, index, new_status) ``` The following are helpers and should only be called via `update_validator_status`: ```python -def activate_validator(index: int, - state: BeaconState) -> None: +def activate_validator(state: BeaconState, + index: int) -> None: """ Activate the validator with the given ``index``. Note that this function mutates ``state``. @@ -1219,16 +1220,23 @@ def activate_validator(index: int, ``` ```python -def initiate_validator_exit(index: int, - state: BeaconState) -> None: +def initiate_validator_exit(state: BeaconState, + index: int) -> None: + """ + Initiate exit for the validator with the given ``index``. + Note that this function mutates ``state``. + """ + if validator.status != ACTIVE: + return + validator = state.validator_registry[index] validator.status = ACTIVE_PENDING_EXIT validator.latest_status_change_slot = state.slot ``` ```python -def exit_validator(index: int, - state: BeaconState, +def exit_validator(state: BeaconState, + index: int, new_status: int) -> None: """ Exit the validator with the given ``index``. @@ -1236,6 +1244,10 @@ def exit_validator(index: int, """ validator = state.validator_registry[index] prev_status = validator.status + + if prev_status == EXITED_WITH_PENALTY: + return + validator.status = new_status validator.latest_status_change_slot = state.slot @@ -1247,7 +1259,7 @@ def exit_validator(index: int, whistleblower.balance += whistleblower_reward validator.balance -= whistleblower_reward - if prev_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY] + if prev_status == EXITED_WITHOUT_PENALTY return # The following updates only occur if not previous exited @@ -1323,7 +1335,7 @@ For each `proposer_slashing` in `block.body.proposer_slashings`: * Verify that `proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard`. * Verify that `proposer_slashing.proposal_data_1.block_hash != proposer_slashing.proposal_data_2.block_hash`. * Verify that `proposer.status != EXITED_WITH_PENALTY`. -* Run `update_validator_status(proposer_slashing.proposer_index, state, new_status=EXITED_WITH_PENALTY)`. +* Run `update_validator_status(state, proposer_slashing.proposer_index, new_status=EXITED_WITH_PENALTY)`. #### Casper slashings @@ -1338,7 +1350,7 @@ For each `casper_slashing` in `block.body.casper_slashings`: * Let `intersection = [x for x in indices(casper_slashing.votes_1) if x in indices(casper_slashing.votes_2)]`. * Verify that `len(intersection) >= 1`. * Verify that `casper_slashing.votes_1.data.justified_slot + 1 < casper_slashing.votes_2.data.justified_slot + 1 == casper_slashing.votes_2.data.slot < casper_slashing.votes_1.data.slot` or `casper_slashing.votes_1.data.slot == casper_slashing.votes_2.data.slot`. -* 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(i, state, 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 `update_validator_status(state, i, new_status=EXITED_WITH_PENALTY)` #### Attestations @@ -1405,7 +1417,7 @@ For each `exit` in `block.body.exits`: * Verify that `validator.status == ACTIVE`. * Verify that `state.slot >= exit.slot`. * Verify that `state.slot >= validator.latest_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD`. -* Run `update_validator_status(validator_index, state, new_status=ACTIVE_PENDING_EXIT)`. +* Run `update_validator_status(state, validator_index, new_status=ACTIVE_PENDING_EXIT)`. ### Ejections @@ -1419,7 +1431,7 @@ def process_ejections(state: BeaconState) -> None: """ for index, validator in enumerate(state.validator_registry): if is_active_validator(validor) and validator.balance < EJECTION_BALANCE: - update_validator_status(index, state, new_status=EXITED_WITHOUT_PENALTY) + update_validator_status(state, index, new_status=EXITED_WITHOUT_PENALTY) ``` ## Per-epoch processing @@ -1560,7 +1572,7 @@ def update_validator_registry(state: BeaconState) -> None: break # Activate validator - update_validator_status(index, state, new_status=ACTIVE) + update_validator_status(state, index, new_status=ACTIVE) # Exit validators within the allowable balance churn balance_churn = 0 @@ -1572,7 +1584,7 @@ def update_validator_registry(state: BeaconState) -> None: break # Exit validator - update_validator_status(index, state, new_status=EXITED_WITHOUT_PENALTY) + update_validator_status(state, index, new_status=EXITED_WITHOUT_PENALTY) # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods From c462f563b14dc3380f2c214381cb7ad137515231 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 11 Dec 2018 10:02:59 -0600 Subject: [PATCH 8/9] use Deposit object for processing intiial deposits --- specs/core/0_beacon-chain.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 7f6edd12b..1a0d4b28f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -50,7 +50,7 @@ - [`ForkData`](#forkdata) - [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) - [Deposit arguments](#deposit-arguments) - - [`Deposit` logs](#deposit-logs) + - [`Eth1Deposit` logs](#eth1deposit-logs) - [`ChainStart` log](#chainstart-log) - [Vyper code](#vyper-code) - [Beacon chain processing](#beacon-chain-processing) @@ -577,9 +577,9 @@ The deposit contract has a single `deposit` function which takes as argument a S We recommend the private key corresponding to `withdrawal_pubkey` be stored in cold storage until a withdrawal is required. -### `Deposit` logs +### `Eth1Deposit` logs -Every deposit, of size between `MIN_DEPOSIT` and `MAX_DEPOSIT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. +Every deposit, of size between `MIN_DEPOSIT` and `MAX_DEPOSIT`, emits an `Eth1Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. ### `ChainStart` log @@ -587,7 +587,7 @@ When sufficiently many full deposits have been made the deposit contract emits t * `genesis_time` equals `time` in the `ChainStart` log * `processed_pow_receipt_root` equals `receipt_root` in the `ChainStart` log -* `initial_validator_entries` is built according to the `Deposit` logs up to the deposit that triggered the `ChainStart` log, processed in the order in which they were emitted (oldest to newest) +* `initial_validator_deposits` is a list of `Deposit` objects built according to the `Eth1Deposit` logs up to the deposit that triggered the `ChainStart` log, processed in the order in which they were emitted (oldest to newest) ### Vyper code @@ -599,7 +599,7 @@ CHAIN_START_FULL_DEPOSIT_THRESHOLD: constant(uint256) = 16384 # 2**14 DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32 SECONDS_PER_DAY: constant(uint256) = 86400 -Deposit: event({previous_receipt_root: bytes32, data: bytes[2064], deposit_count: uint256}) +Eth1Deposit: event({previous_receipt_root: bytes32, data: bytes[2064], deposit_count: uint256}) ChainStart: event({receipt_root: bytes32, time: bytes[8]}) receipt_tree: bytes32[uint256] @@ -617,7 +617,7 @@ def deposit(deposit_parameters: bytes[2048]): timestamp_bytes8: bytes[8] = slice(concat("", convert(block.timestamp, bytes32)), start=24, len=8) deposit_data: bytes[2064] = concat(msg_gwei_bytes8, timestamp_bytes8, deposit_parameters) - log.Deposit(self.receipt_tree[1], deposit_data, self.deposit_count) + log.Eth1Deposit(self.receipt_tree[1], deposit_data, self.deposit_count) # add deposit to merkle tree self.receipt_tree[index] = sha3(deposit_data) @@ -1042,7 +1042,7 @@ A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the follow `STARTUP_STATE_ROOT` is the root of the initial state, computed by running the following code: ```python -def on_startup(initial_validator_entries: List[Any], +def on_startup(initial_validator_deposits: List[Deposit], genesis_time: int, processed_pow_receipt_root: Hash32) -> BeaconState: state = BeaconState( @@ -1056,7 +1056,7 @@ def on_startup(initial_validator_entries: List[Any], ), # Validator registry - validator_registry=initial_validator_registry, + validator_registry=[], validator_registry_latest_change_slot=INITIAL_SLOT_NUMBER, validator_registry_exit_count=0, validator_registry_delta_chain_tip=ZERO_HASH, @@ -1086,14 +1086,14 @@ def on_startup(initial_validator_entries: List[Any], ) # handle initial deposits and activations - for pubkey, deposit, proof_of_possession, withdrawal_credentials, randao_commitment in initial_validator_entries: + for deposit in initial_validator_deposits: validator_index = process_deposit( state=state, - pubkey=pubkey, - deposit=deposit, - proof_of_possession=proof_of_possession, - withdrawal_credentials=withdrawal_credentials, - randao_commitment=randao_commitment + pubkey=deposit.deposit_data.deposit_parameters.pubkey, + deposit=deposit.deposit_data.value, + proof_of_possession=deposit.deposit_data.deposit_parameters.proof_of_possession, + withdrawal_credentials=deposit.deposit_data.deposit_parameters.withdrawal_credentials, + randao_commitment=deposit.deposit_data.deposit_parameters.randao_commitment ) if state.validator_registry[index].balance >= MAX_DEPOSIT: update_validator_status(state, index, ACTIVE) From 5734b25fb19a1e7512b70b60952ee19eef85d720 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 11 Dec 2018 10:13:31 -0600 Subject: [PATCH 9/9] rename bls verification spec ref --- 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 1a0d4b28f..a9f5150ea 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1013,7 +1013,7 @@ def integer_squareroot(n: int) -> int: #### `BLSVerify` -`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS12-381 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). +`BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS Verification spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). ### On startup