def process_rewards_and_penalties( state: BeaconState, config: Eth2Config ) -> BeaconState: current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) if current_epoch == config.GENESIS_EPOCH: return state rewards_for_attestations, penalties_for_attestations = get_attestation_deltas( state, config ) rewards_for_crosslinks, penalties_for_crosslinks = get_crosslink_deltas( state, config ) for index in range(len(state.validators)): index = ValidatorIndex(index) state = increase_balance( state, index, Gwei(rewards_for_attestations[index] + rewards_for_crosslinks[index]), ) state = decrease_balance( state, index, Gwei(penalties_for_attestations[index] + penalties_for_crosslinks[index]), ) return state
def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> BeaconState: return state.copy(balances=update_tuple_item_with_fn( state.balances, index, lambda balance, *_: Gwei(0) if delta > balance else Gwei(balance - delta), ))
def get_crosslink_deltas( state: BeaconState, config: Eth2Config) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: rewards = tuple(0 for _ in range(len(state.validators))) penalties = tuple(0 for _ in range(len(state.validators))) epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) active_validators_indices = get_active_validator_indices( state.validators, epoch) epoch_committee_count = get_committee_count( len(active_validators_indices), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) epoch_start_shard = get_start_shard( state, epoch, CommitteeConfig(config), ) for shard_offset in range(epoch_committee_count): shard = Shard((epoch_start_shard + shard_offset) % config.SHARD_COUNT) crosslink_committee = set( get_crosslink_committee( state, epoch, shard, CommitteeConfig(config), )) _, attesting_indices = get_winning_crosslink_and_attesting_indices( state=state, epoch=epoch, shard=shard, config=config, ) total_attesting_balance = get_total_balance( state, attesting_indices, ) total_committee_balance = get_total_balance( state, crosslink_committee, ) for index in crosslink_committee: base_reward = get_base_reward(state, index, config) if index in attesting_indices: rewards = update_tuple_item_with_fn( rewards, index, lambda balance, delta: balance + delta, base_reward * total_attesting_balance // total_committee_balance) else: penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, base_reward, ) return tuple(Gwei(reward) for reward in rewards), tuple( Gwei(penalty) for penalty in penalties)
def get_base_reward(*, state: 'BeaconState', index: ValidatorIndex, base_reward_quotient: int, previous_total_balance: Gwei, max_deposit_amount: Gwei) -> Gwei: if previous_total_balance == 0: return Gwei(0) adjusted_quotient = (integer_squareroot(previous_total_balance) // base_reward_quotient) return Gwei( get_effective_balance( state.validator_balances, index, max_deposit_amount, ) // adjusted_quotient // 5)
def slash_validator(state: BeaconState, index: ValidatorIndex, config: Eth2Config, whistleblower_index: ValidatorIndex = None) -> BeaconState: """ Slash the validator with index ``index``. Exit the validator, penalize the validator, and reward the whistleblower. """ # NOTE: remove in phase 1 assert whistleblower_index is None slots_per_epoch = config.SLOTS_PER_EPOCH current_epoch = state.current_epoch(slots_per_epoch) state = initiate_validator_exit(state, index, config) state = state.update_validator_with_fn( index, _set_validator_slashed, current_epoch, config.EPOCHS_PER_SLASHINGS_VECTOR, ) slashed_balance = state.validators[index].effective_balance slashed_epoch = current_epoch % config.EPOCHS_PER_SLASHINGS_VECTOR state = state.copy(slashings=update_tuple_item_with_fn( state.slashings, slashed_epoch, lambda balance, slashed_balance: Gwei(balance + slashed_balance), slashed_balance, )) state = decrease_balance( state, index, slashed_balance // config.MIN_SLASHING_PENALTY_QUOTIENT) proposer_index = get_beacon_proposer_index(state, CommitteeConfig(config)) if whistleblower_index is None: whistleblower_index = proposer_index whistleblower_reward = Gwei(slashed_balance // config.WHISTLEBLOWER_REWARD_QUOTIENT) proposer_reward = Gwei(whistleblower_reward // config.PROPOSER_REWARD_QUOTIENT) state = increase_balance(state, proposer_index, proposer_reward) state = increase_balance( state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward), ) return state
def slash_validator( state: BeaconState, index: ValidatorIndex, config: Eth2Config, whistleblower_index: ValidatorIndex = None, ) -> BeaconState: """ Slash the validator with index ``index``. Exit the validator, penalize the validator, and reward the whistleblower. """ # NOTE: remove in phase 1 assert whistleblower_index is None slots_per_epoch = config.SLOTS_PER_EPOCH current_epoch = state.current_epoch(slots_per_epoch) state = initiate_validator_exit(state, index, config) state = state.transform( ("validators", index), partial( _set_validator_slashed, current_epoch=current_epoch, epochs_per_slashings_vector=config.EPOCHS_PER_SLASHINGS_VECTOR, ), ) slashed_balance = state.validators[index].effective_balance slashed_epoch = current_epoch % config.EPOCHS_PER_SLASHINGS_VECTOR state = state.transform(("slashings", slashed_epoch), lambda balance: Gwei(balance + slashed_balance)) state = decrease_balance( state, index, slashed_balance // config.MIN_SLASHING_PENALTY_QUOTIENT) proposer_index = get_beacon_proposer_index(state, config) if whistleblower_index is None: whistleblower_index = proposer_index whistleblower_reward = Gwei(slashed_balance // config.WHISTLEBLOWER_REWARD_QUOTIENT) proposer_reward = Gwei(whistleblower_reward // config.PROPOSER_REWARD_QUOTIENT) state = increase_balance(state, proposer_index, proposer_reward) state = increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) return state
def get_total_attesting_balance( *, state: 'BeaconState', shard: ShardNumber, shard_block_root: Hash32, attestations: Sequence[PendingAttestationRecord], genesis_epoch: EpochNumber, epoch_length: int, max_deposit_amount: Gwei, target_committee_size: int, shard_count: int) -> Gwei: return Gwei( sum( get_effective_balance(state.validator_balances, i, max_deposit_amount) for i in get_attesting_validator_indices( state=state, attestations=attestations, shard=shard, shard_block_root=shard_block_root, genesis_epoch=genesis_epoch, epoch_length=epoch_length, target_committee_size=target_committee_size, shard_count=shard_count, ) ) )
def sample_deposit_data_params(sample_signature): return { 'pubkey': BLSPubkey(b'\x67' * 48), 'withdrawal_credentials': b'\11' * 32, 'amount': Gwei(56), 'signature': sample_signature, }
def create_pending_validator(cls, pubkey: BLSPubkey, withdrawal_credentials: Hash32, amount: Gwei, config: Eth2Config) -> 'Validator': """ Return a new pending ``Validator`` with the given fields. """ return cls( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, effective_balance=Gwei( min( _round_down_to_previous_multiple( amount, config.EFFECTIVE_BALANCE_INCREMENT, ), config.MAX_EFFECTIVE_BALANCE, ) ), activation_eligibility_epoch=FAR_FUTURE_EPOCH, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, )
def get_effective_balance(validator_balances: Sequence[Gwei], index: ValidatorIndex, max_deposit: Ether) -> Gwei: """ Return the effective balance (also known as "balance at stake") for a ``validator`` with the given ``index``. """ return min(validator_balances[index], Gwei(max_deposit * GWEI_PER_ETH))
def get_winning_root( *, state: 'BeaconState', shard: ShardNumber, attestations: Sequence[PendingAttestationRecord], max_deposit_amount: Gwei, committee_config: CommitteeConfig) -> Tuple[Hash32, Gwei]: winning_root = None winning_root_balance: Gwei = Gwei(0) shard_block_roots = set( [ a.data.shard_block_root for a in attestations if a.data.shard == shard ] ) for shard_block_root in shard_block_roots: total_attesting_balance = get_total_attesting_balance( state=state, shard=shard, shard_block_root=shard_block_root, attestations=attestations, max_deposit_amount=max_deposit_amount, committee_config=committee_config, ) if total_attesting_balance > winning_root_balance: winning_root = shard_block_root winning_root_balance = total_attesting_balance elif total_attesting_balance == winning_root_balance and winning_root_balance > 0: if shard_block_root < winning_root: winning_root = shard_block_root if winning_root is None: raise NoWinningRootError return (winning_root, winning_root_balance)
def calculate_effective_balance(amount: Gwei, config: Eth2Config) -> Gwei: return Gwei( min( _round_down_to_previous_multiple( amount, config.EFFECTIVE_BALANCE_INCREMENT), config.MAX_EFFECTIVE_BALANCE, ))
def sample_deposit_data_params(sample_signature): return { "pubkey": BLSPubkey(b"\x67" * 48), "withdrawal_credentials": b"\11" * 32, "amount": Gwei(56), "signature": sample_signature, }
def get_winning_root(*, state: 'BeaconState', shard: ShardNumber, attestations: Sequence[PendingAttestationRecord], genesis_epoch: EpochNumber, epoch_length: int, max_deposit_amount: Gwei, target_committee_size: int, shard_count: int) -> Tuple[Hash32, Gwei]: winning_root = None winning_root_balance: Gwei = Gwei(0) shard_block_roots = set([ a.data.shard_block_root for a in attestations if a.data.shard == shard ]) for shard_block_root in shard_block_roots: total_attesting_balance = get_total_attesting_balance( state=state, shard=shard, shard_block_root=shard_block_root, attestations=attestations, genesis_epoch=genesis_epoch, epoch_length=epoch_length, max_deposit_amount=max_deposit_amount, target_committee_size=target_committee_size, shard_count=shard_count, ) if total_attesting_balance > winning_root_balance: winning_root = shard_block_root winning_root_balance = total_attesting_balance elif total_attesting_balance == winning_root_balance and winning_root_balance > 0: if shard_block_root < winning_root: winning_root = shard_block_root if winning_root is None: raise NoWinningRootError return (winning_root, winning_root_balance)
def get_winning_root(*, state: 'BeaconState', shard: Shard, attestations: Sequence[PendingAttestationRecord], max_deposit_amount: Gwei, committee_config: CommitteeConfig) -> Tuple[Hash32, Gwei]: winning_root = None winning_root_balance: Gwei = Gwei(0) crosslink_data_roots = set([ a.data.crosslink_data_root for a in attestations if a.data.shard == shard ]) for crosslink_data_root in crosslink_data_roots: attesting_validator_indices = get_attester_indices_from_attesttion( state=state, attestations=[ a for a in attestations if a.data.shard == shard and a.data.crosslink_data_root == crosslink_data_root ], committee_config=committee_config, ) total_attesting_balance = get_total_balance( state.validator_balances, attesting_validator_indices, max_deposit_amount, ) if total_attesting_balance > winning_root_balance: winning_root = crosslink_data_root winning_root_balance = total_attesting_balance elif total_attesting_balance == winning_root_balance and winning_root_balance > 0: if crosslink_data_root < winning_root: winning_root = crosslink_data_root if winning_root is None: raise NoWinningRootError return (winning_root, winning_root_balance)
def get_logs(self, block_number: BlockNumber) -> Tuple[DepositLog, ...]: block_hash = block_number.to_bytes(32, byteorder='big') if block_number == self.start_block_number: logs = ( DepositLog( block_hash=Hash32(block_hash), pubkey=deposit.pubkey, withdrawal_credentials=deposit.withdrawal_credentials, signature=deposit.signature, amount=deposit.amount, ) for deposit in self.deposits ) return tuple(logs) else: logs = ( DepositLog( block_hash=Hash32(block_hash), pubkey=BLSPubkey(b'\x12' * 48), withdrawal_credentials=Hash32(b'\x23' * 32), signature=BLSSignature(b'\x34' * 96), amount=Gwei(32 * GWEI_PER_ETH), ) for _ in range(self.num_deposits_per_block) ) return tuple(logs)
def _compute_next_slashings(state: BeaconState, config: Eth2Config) -> Tuple[Gwei, ...]: next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) return update_tuple_item( state.slashings, next_epoch % config.EPOCHS_PER_SLASHINGS_VECTOR, Gwei(0), )
def get_total_balance(state: 'BeaconState', validator_indices: Sequence[ValidatorIndex]) -> Gwei: """ Return the combined effective balance of an array of validators. """ return Gwei( max( sum(state.validators[index].effective_balance for index in validator_indices), 1))
def get_base_reward(*, state: 'BeaconState', index: ValidatorIndex, base_reward_quotient: int, max_deposit_amount: Gwei) -> Gwei: return Gwei( get_effective_balance( state.validator_balances, index, max_deposit_amount, ) // base_reward_quotient // 5)
def from_contract_log_dict(cls, log: Dict[Any, Any]) -> "DepositLog": log_args = log["args"] return cls( block_hash=log["blockHash"], pubkey=log_args["pubkey"], withdrawal_credentials=log_args["withdrawal_credentials"], amount=Gwei(int.from_bytes(log_args["amount"], "little")), signature=log_args["signature"], )
def test_process_registry_updates(validator_count, genesis_state, config, slots_per_epoch): eligible_index = len(genesis_state.validators) activation_index = len(genesis_state.validators) + 1 exiting_index = len(genesis_state.validators) - 1 eligible_validator = Validator.create_pending_validator( pubkey=b"\x10" * 48, withdrawal_credentials=b"\x11" * 32, amount=Gwei(32 * GWEI_PER_ETH), config=config, ) activating_validator = eligible_validator.set( "activation_eligibility_epoch", genesis_state.finalized_checkpoint.epoch) state = genesis_state.mset( "validators", genesis_state.validators[:exiting_index] + (genesis_state.validators[exiting_index].set( "effective_balance", config.EJECTION_BALANCE - 1), ) + (eligible_validator, activating_validator), "balances", genesis_state.balances + (config.MAX_EFFECTIVE_BALANCE, config.MAX_EFFECTIVE_BALANCE), ) # handles activations post_state = process_registry_updates(state, config) # Check if the eligible_validator is eligible pre_eligible_validator = state.validators[eligible_index] post_eligible_validator = post_state.validators[eligible_index] assert pre_eligible_validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH assert pre_eligible_validator.activation_epoch == FAR_FUTURE_EPOCH assert (post_eligible_validator.activation_eligibility_epoch == state.current_epoch(slots_per_epoch) + 1) # Check if the activating_validator is activated pre_activation_validator = state.validators[activation_index] post_activation_validator = post_state.validators[activation_index] assert pre_activation_validator.activation_epoch == FAR_FUTURE_EPOCH assert post_activation_validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH activation_epoch = compute_activation_exit_epoch( state.current_epoch(config.SLOTS_PER_EPOCH), config.MAX_SEED_LOOKAHEAD) assert post_activation_validator.is_active(activation_epoch) # Check if the exiting_validator is exited pre_exiting_validator = state.validators[exiting_index] post_exiting_validator = post_state.validators[exiting_index] assert pre_exiting_validator.exit_epoch == FAR_FUTURE_EPOCH assert pre_exiting_validator.withdrawable_epoch == FAR_FUTURE_EPOCH assert state.validators[ exiting_index].effective_balance <= config.EJECTION_BALANCE assert post_exiting_validator.exit_epoch != FAR_FUTURE_EPOCH assert post_exiting_validator.withdrawable_epoch != FAR_FUTURE_EPOCH assert post_exiting_validator.withdrawable_epoch > post_exiting_validator.exit_epoch
def _update_rewards_or_penalies( index: ValidatorIndex, amount: Gwei, rewards_or_penalties: Dict[ValidatorIndex, Gwei] ) -> Iterable[Tuple[ValidatorIndex, Gwei]]: for i in rewards_or_penalties: if i == index: yield i, Gwei(rewards_or_penalties[i] + amount) else: yield i, rewards_or_penalties[i]
def get_total_balance_from_effective_balances( effective_balances: Dict[ValidatorIndex, Gwei], validator_indices: Sequence[ValidatorIndex]) -> Gwei: return Gwei( sum( effective_balances[index] for index in validator_indices ) )
def _compute_total_penalties(state: BeaconState, config: BeaconConfig, current_epoch: Epoch) -> Gwei: epoch_index = current_epoch % config.LATEST_SLASHED_EXIT_LENGTH start_index_in_latest_slashed_balances = ( (epoch_index + 1) % config.LATEST_SLASHED_EXIT_LENGTH) total_at_start = state.latest_slashed_balances[ start_index_in_latest_slashed_balances] total_at_end = state.latest_slashed_balances[epoch_index] return Gwei(total_at_end - total_at_start)
def _process_rewards_and_penalties_for_crosslinks( state: BeaconState, config: Eth2Config, effective_balances: Dict[ValidatorIndex, Gwei], base_rewards: Dict[ValidatorIndex, Gwei] ) -> Tuple[Dict[ValidatorIndex, Gwei], Dict[ValidatorIndex, Gwei]]: # noqa: E501 previous_epoch_start_slot = get_epoch_start_slot( state.previous_epoch(config.SLOTS_PER_EPOCH), config.SLOTS_PER_EPOCH, ) current_epoch_start_slot = get_epoch_start_slot( state.current_epoch(config.SLOTS_PER_EPOCH), config.SLOTS_PER_EPOCH, ) rewards_received = { ValidatorIndex(index): Gwei(0) for index in range(len(state.validator_registry)) } penalties_received = rewards_received.copy() for slot in range(previous_epoch_start_slot, current_epoch_start_slot): crosslink_committees_at_slot = get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), ) for crosslink_committee, shard in crosslink_committees_at_slot: winning_root, attesting_validator_indices = get_winning_root_and_participants( state=state, shard=shard, effective_balances=effective_balances, committee_config=CommitteeConfig(config), ) total_attesting_balance = get_total_balance( state.validator_balances, attesting_validator_indices, config.MAX_DEPOSIT_AMOUNT, ) total_balance = get_total_balance_from_effective_balances( effective_balances, crosslink_committee, ) for index in attesting_validator_indices: rewards_received = _update_rewards_or_penalies( index, base_rewards[index] * total_attesting_balance // total_balance, rewards_received, ) for index in set(crosslink_committee).difference( attesting_validator_indices): penalties_received = _update_rewards_or_penalies( index, base_rewards[index], penalties_received, ) return (rewards_received, penalties_received)
def get_inactivity_penalty( *, base_reward: Gwei, effective_balance: Gwei, epochs_since_finality: int, inactivity_penalty_quotient: int) -> Gwei: return Gwei( base_reward + effective_balance * epochs_since_finality // inactivity_penalty_quotient // 2 )
def get_total_balance(validator_balances: Sequence[Gwei], validator_indices: Sequence[ValidatorIndex], max_deposit_amount: Gwei) -> Gwei: """ Return the combined effective balance of an array of validators. """ return Gwei(sum( get_effective_balance(validator_balances, index, max_deposit_amount) for index in validator_indices ))
def sample_validator_record_params(): return { 'pubkey': b'\x67' * 48, 'withdrawal_credentials': b'\x01' * 32, 'effective_balance': Gwei(32 * GWEI_PER_ETH), 'slashed': False, 'activation_eligibility_epoch': FAR_FUTURE_EPOCH, 'activation_epoch': FAR_FUTURE_EPOCH, 'exit_epoch': FAR_FUTURE_EPOCH, 'withdrawable_epoch': FAR_FUTURE_EPOCH, }
def sample_validator_record_params(): return { "pubkey": b"\x67" * 48, "withdrawal_credentials": b"\x01" * 32, "effective_balance": Gwei(32 * GWEI_PER_ETH), "slashed": False, "activation_eligibility_epoch": FAR_FUTURE_EPOCH, "activation_epoch": FAR_FUTURE_EPOCH, "exit_epoch": FAR_FUTURE_EPOCH, "withdrawable_epoch": FAR_FUTURE_EPOCH, }
def _determine_slashing_penalty(total_penalties: Gwei, total_balance: Gwei, balance: Gwei, min_slashing_penalty_quotient: int) -> Gwei: collective_penalty = min(total_penalties * 3, total_balance) // total_balance return Gwei( max( balance * collective_penalty, balance // min_slashing_penalty_quotient ) )