diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 3e62223d1..03635cbf9 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -51,11 +51,12 @@ func process_deposit(state: var BeaconState, proof_of_possession: ValidatorSig, withdrawal_credentials: Eth2Digest, randao_commitment: Eth2Digest, - flags: UpdateFlags): Uint24 = + custody_commitment: Eth2Digest) : Uint24 = ## Process a deposit from Ethereum 1.0. - if skipValidation notin flags: - # TODO return error + if false: + # TODO return error; currently, just fails if ever called + # but hadn't been set up to run at all doAssert validate_proof_of_possession( state, pubkey, proof_of_possession, withdrawal_credentials, randao_commitment) @@ -65,13 +66,20 @@ func process_deposit(state: var BeaconState, if pubkey notin validator_pubkeys: # Add new validator let validator = ValidatorRecord( + status: UNUSED, pubkey: pubkey, withdrawal_credentials: withdrawal_credentials, randao_commitment: randao_commitment, randao_layers: 0, - status: PENDING_ACTIVATION, - latest_status_change_slot: state.slot, - exit_count: 0 + activation_slot: FAR_FUTURE_SLOT, + exit_slot: FAR_FUTURE_SLOT, + withdrawal_slot: FAR_FUTURE_SLOT, + penalized_slot: FAR_FUTURE_SLOT, + exit_count: 0, + status_flags: 0, + custody_commitment: custody_commitment, + latest_custody_reseed_slot: GENESIS_SLOT, + penultimate_custody_reseed_slot: GENESIS_SLOT ) let index = min_empty_validator_index( @@ -85,7 +93,7 @@ func process_deposit(state: var BeaconState, state.validator_balances[index.get()] = deposit index.get().Uint24 else: - # Increase balance by deposit + # Increase balance by deposit amount let index = validator_pubkeys.find(pubkey) let validator = addr state.validator_registry[index] assert state.validator_registry[index].withdrawal_credentials == @@ -95,20 +103,19 @@ func process_deposit(state: var BeaconState, index.Uint24 func activate_validator(state: var BeaconState, - index: Uint24) = + index: Uint24, + genesis: bool) = ## Activate the validator with the given ``index``. let validator = addr state.validator_registry[index] - if validator.status != PENDING_ACTIVATION: - return - validator.status = ACTIVE - validator.latest_status_change_slot = state.slot + validator.activation_slot = if genesis: GENESIS_SLOT else: state.slot + ENTRY_EXIT_DELAY state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( state.validator_registry_delta_chain_tip, index, validator.pubkey, + validator.activation_slot, ACTIVATION, ) @@ -122,9 +129,9 @@ func initiate_validator_exit(state: var BeaconState, validator.status = ACTIVE_PENDING_EXIT validator.latest_status_change_slot = state.slot -func exit_validator(state: var BeaconState, - index: Uint24, - new_status: ValidatorStatusCodes) = +func exit_validator*(state: var BeaconState, + index: Uint24, + new_status: ValidatorStatusCodes) = ## Exit the validator with the given ``index``. let @@ -162,6 +169,7 @@ func exit_validator(state: var BeaconState, state.validator_registry_delta_chain_tip, index, validator.pubkey, + validator.exit_slot, ValidatorSetDeltaFlags.EXIT ) @@ -172,17 +180,43 @@ func exit_validator(state: var BeaconState, committee.delete(i) break -func update_validator_status*(state: var BeaconState, - index: Uint24, - new_status: ValidatorStatusCodes) = - ## Update the validator status with the given ``index`` to ``new_status``. - ## Handle other general accounting related to this status update. - 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) +func process_penalties_and_exits_eligible(state: BeaconState, index: int): bool = + let validator = state.validator_registry[index] + if validator.penalized_slot <= state.slot: + # strangely uppercase variable-ish name + let PENALIZED_WITHDRAWAL_TIME = (LATEST_PENALIZED_EXIT_LENGTH * EPOCH_LENGTH div 2).uint64 + return state.slot >= validator.penalized_slot + PENALIZED_WITHDRAWAL_TIME + else: + return state.slot >= validator.exit_slot + MIN_VALIDATOR_WITHDRAWAL_TIME + +func process_penalties_and_exits(state: var BeaconState) = + # The active validators + let active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) + # The total effective balance of active validators + var total_balance : uint64 = 0 + for i in active_validator_indices: + total_balance += get_effective_balance(state, i) + + for index, validator in state.validator_registry: + if (state.slot div EPOCH_LENGTH) == (validator.penalized_slot div EPOCH_LENGTH) + LATEST_PENALIZED_EXIT_LENGTH div 2: + let + e = ((state.slot div EPOCH_LENGTH) mod LATEST_PENALIZED_EXIT_LENGTH).int + total_at_start = state.latest_penalized_exit_balances[(e + 1) mod 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, index.Uint24) * min(total_penalties * 3, total_balance) div total_balance + state.validator_balances[index] -= penalty + + ## 'state' is of type which cannot be captured as it + ## would violate memory safety, when using nested function approach in + ## spec directly. That said, the spec approach evidently is not meant, + ## based on its abundant and pointless memory copies, for production. + var eligible_indices : seq[Uint24] = @[] + for i in 0 ..< len(state.validator_registry): + eligible_indices.add i.Uint24 + + ## TODO figure out that memory safety issue, which would come up again when + ## sorting, and then actually do withdrawals func get_initial_beacon_state*( initial_validator_deposits: openArray[Deposit], @@ -219,16 +253,25 @@ func get_initial_beacon_state*( validator_registry_exit_count: 0, validator_registry_delta_chain_tip: ZERO_HASH, + # Randomness and committees + previous_epoch_start_shard: GENESIS_START_SHARD, + current_epoch_start_shard: GENESIS_START_SHARD, + previous_epoch_calculation_slot: GENESIS_SLOT, + current_epoch_calculation_slot: GENESIS_SLOT, + previous_epoch_randao_mix: ZERO_HASH, + current_epoch_randao_mix: ZERO_HASH, + # Finality previous_justified_slot: GENESIS_SLOT, justified_slot: GENESIS_SLOT, + justification_bitfield: 0, finalized_slot: GENESIS_SLOT, - # PoW receipt root + # Deposit root latest_deposit_root: latest_deposit_root, ) - # handle initial deposits and activations + # Process initial deposits for deposit in initial_validator_deposits: let validator_index = process_deposit( state, @@ -237,10 +280,17 @@ func get_initial_beacon_state*( deposit.deposit_data.deposit_input.proof_of_possession, deposit.deposit_data.deposit_input.withdrawal_credentials, deposit.deposit_data.deposit_input.randao_commitment, - flags + deposit.deposit_data.deposit_input.custody_commitment, ) + if state.validator_balances[validator_index] >= MAX_DEPOSIT: - update_validator_status(state, validator_index, ACTIVE) + activate_validator(state, validator_index, true) + + # Process initial activations + #for validator_index in 0 ..< state.validator_registry.len: + # let vi = validator_index.Uint24 + # if get_effective_balance(state, vi) > MAX_DEPOSIT * GWEI_PER_ETH: + # activate_validator(state, vi, true) # set initial committee shuffling let @@ -323,7 +373,7 @@ func update_validator_registry*(state: var BeaconState) = # Activate validators within the allowable balance churn var balance_churn = 0'u64 for index, validator in state.validator_registry: - if validator.status == PENDING_ACTIVATION and + if validator.activation_slot > state.slot + ENTRY_EXIT_DELAY and state.validator_balances[index] >= MAX_DEPOSIT * GWEI_PER_ETH: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index.Uint24) @@ -331,37 +381,20 @@ func update_validator_registry*(state: var BeaconState) = break # Activate validator - update_validator_status(state, index.Uint24, ACTIVE) + activate_validator(state, index.Uint24, false) # Exit validators within the allowable balance churn balance_churn = 0 for index, validator in state.validator_registry: - if validator.status == ACTIVE_PENDING_EXIT: + if (validator.exit_slot > state.slot + ENTRY_EXIT_DELAY) and + ((validator.status_flags and INITIATED_EXIT) == INITIATED_EXIT): # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index.Uint24) if balance_churn > max_balance_churn: break # Exit validator - update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY) - - # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods - let - period_index = (state.slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int - total_penalties = ( - (state.latest_penalized_exit_balances[period_index]) + - (if period_index >= 1: - state.latest_penalized_exit_balances[period_index - 1] else: 0) + - (if period_index >= 2: - state.latest_penalized_exit_balances[period_index - 2] else: 0) - ) - - # Calculate penalties for slashed validators - for index, validator in state.validator_registry: - if validator.status == EXITED_WITH_PENALTY: - state.validator_balances[index] -= - get_effective_balance(state, index.Uint24) * - min(total_penalties * 3, total_balance) div total_balance + exit_validator(state, index.Uint24, EXITED_WITHOUT_PENALTY) # Perform additional updates state.previous_epoch_calculation_slot = state.current_epoch_calculation_slot @@ -373,7 +406,7 @@ func update_validator_registry*(state: var BeaconState) = # TODO "If a validator registry update does not happen do the following: ..." - #process_penalties_and_exits(state) + process_penalties_and_exits(state) proc checkAttestation*( state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool = diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index 2a1d77244..045df9b6e 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -78,7 +78,7 @@ const LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13 LATEST_RANDAO_MIXES_LENGTH* = 2'u64^13 LATEST_PENALIZED_EXIT_LENGTH* = 8192 # epochs - MAX_WITHDRAWALS_PER_EPOCHS* = 4 # withdrawals + MAX_WITHDRAWALS_PER_EPOCH* = 4 # withdrawals # Deposit contract DEPOSIT_CONTRACT_TREE_DEPTH* = 2^5 @@ -132,6 +132,9 @@ const COLLECTIVE_PENALTY_CALCULATION_PERIOD* = 2'u64^20 ##\ ## slots (~73 days) + ENTRY_EXIT_DELAY* = 256 ##\ + ## slots (~25.6 minutes) + ZERO_BALANCE_VALIDATOR_TTL* = 2'u64^22 ##\ ## slots (~291 days) @@ -390,10 +393,15 @@ type exit_count*: uint64 ##\ ## Exit counter when validator exited (or 0) + status_flags*: uint64 + custody_commitment*: Eth2Digest - last_poc_change_slot*: uint64 - second_last_poc_change_slot*: uint64 + latest_custody_reseed_slot*: uint64 ##\ + ## Slot of latest custody reseed + + penultimate_custody_reseed_slot*: uint64 ##\ + ## Slot of second-latest custody reseed CrosslinkRecord* = object slot*: uint64 @@ -438,10 +446,11 @@ type latest_registry_delta_root*: Eth2Digest validator_index*: Uint24 pubkey*: ValidatorPubKey + slot*: uint64 flag*: ValidatorSetDeltaFlags ValidatorStatusCodes* {.pure.} = enum - PENDING_ACTIVATION = 0 + UNUSED = 0 ACTIVE = 1 ACTIVE_PENDING_EXIT = 2 EXITED_WITHOUT_PENALTY = 3 diff --git a/beacon_chain/spec/validator.nim b/beacon_chain/spec/validator.nim index 1aca2144b..809d46b57 100644 --- a/beacon_chain/spec/validator.nim +++ b/beacon_chain/spec/validator.nim @@ -68,6 +68,7 @@ func get_new_validator_registry_delta_chain_tip*( current_validator_registry_delta_chain_tip: Eth2Digest, index: Uint24, pubkey: ValidatorPubKey, + slot: uint64, flag: ValidatorSetDeltaFlags): Eth2Digest = ## Compute the next hash in the validator registry delta hash chain. @@ -75,5 +76,6 @@ func get_new_validator_registry_delta_chain_tip*( latest_registry_delta_root: current_validator_registry_delta_chain_tip, validator_index: index, pubkey: pubkey, + slot: slot, flag: flag )) diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index 263b56661..84b630fe0 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -101,7 +101,7 @@ func processRandao( return true func processDepositRoot(state: var BeaconState, blck: BeaconBlock) = - ## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#pow-receipt-root + ## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#deposit-root for x in state.deposit_roots.mitems(): if blck.deposit_root == x.deposit_root: @@ -113,6 +113,18 @@ func processDepositRoot(state: var BeaconState, blck: BeaconBlock) = vote_count: 1 ) +func penalizeValidator(state: var BeaconState, index: Uint24) = + exit_validator(state, index, EXITED_WITH_PENALTY) + var validator = state.validator_registry[index] + #state.latest_penalized_exit_balances[(state.slot div EPOCH_LENGTH) mod LATEST_PENALIZED_EXIT_LENGTH] += get_effective_balance(state, index.Uint24) + + let + whistleblower_index = get_beacon_proposer_index(state, state.slot) + whistleblower_reward = get_effective_balance(state, index) div WHISTLEBLOWER_REWARD_QUOTIENT + state.validator_balances[whistleblower_index] += whistleblower_reward + state.validator_balances[index] -= whistleblower_reward + validator.penalized_slot = state.slot + proc processProposerSlashings( state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = ## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#proposer-slashings-1 @@ -159,12 +171,11 @@ proc processProposerSlashings( warn("PropSlash: block root mismatch") return false - if not (proposer.status != EXITED_WITH_PENALTY): - warn("PropSlash: wrong status") + if not (proposer.penalized_slot > state.slot): + warn("PropSlash: penalized slot") return false - update_validator_status( - state, proposer_slashing.proposer_index, EXITED_WITH_PENALTY) + penalizeValidator(state, proposer_slashing.proposer_index) return true @@ -226,7 +237,7 @@ proc processCasperSlashings(state: var BeaconState, blck: BeaconBlock): bool = for i in intersection: if state.validator_registry[i].status != EXITED_WITH_PENALTY: - update_validator_status(state, i, EXITED_WITH_PENALTY) + exit_validator(state, i, EXITED_WITH_PENALTY) return true @@ -264,6 +275,10 @@ proc processDeposits(state: var BeaconState, blck: BeaconBlock): bool = # TODO! Spec writing in progress true +func initiate_validator_exit(state: var BeaconState, index: int) = + var validator = state.validator_registry[index] + validator.status_flags = validator.status_flags or INITIATED_EXIT + proc processExits( state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = ## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#exits-1 @@ -281,20 +296,16 @@ proc processExits( warn("Exit: invalid signature") return false - if not (validator.status == ACTIVE): - warn("Exit: validator not active") + if not (validator.exit_slot > state.slot + ENTRY_EXIT_DELAY): + warn("Exit: exit/entry too close") return false if not (state.slot >= exit.slot): warn("Exit: bad slot") return false - if not (state.slot >= - validator.latest_status_change_slot + - SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD): - warn("Exit: not within committee change period") - - update_validator_status(state, exit.validator_index, ACTIVE_PENDING_EXIT) + exit_validator(state, exit.validator_index, ACTIVE_PENDING_EXIT) + initiate_validator_exit(state, exit.validator_index) return true @@ -307,7 +318,7 @@ proc process_ejections(state: var BeaconState) = for index, validator in state.validator_registry: if is_active_validator(validator) and state.validator_balances[index] < EJECTION_BALANCE: - update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY) + exit_validator(state, index.Uint24, EXITED_WITHOUT_PENALTY) func processSlot(state: var BeaconState, previous_block_root: Eth2Digest) = ## Time on the beacon chain moves in slots. Every time we make it to a new @@ -563,7 +574,7 @@ func processEpoch(state: var BeaconState) = func total_balance_sac(shard_committee: ShardCommittee): uint64 = sum_effective_balances(statePtr[], shard_committee.committee) - block: # Receipt roots + block: # Deposit roots if state.slot mod POW_RECEIPT_ROOT_VOTING_PERIOD == 0: for x in state.deposit_roots: if x.vote_count * 2 >= POW_RECEIPT_ROOT_VOTING_PERIOD: