def get_epoch_boundary_attesting_balances( current_epoch: Epoch, previous_epoch: Epoch, state: 'BeaconState', config: 'BeaconConfig') -> Tuple[Gwei, Gwei]: current_epoch_attestations = get_current_epoch_attestations( state, config.SLOTS_PER_EPOCH) previous_epoch_attestations = get_previous_epoch_attestations( state, config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH, ) previous_epoch_boundary_root = get_block_root( state, get_epoch_start_slot(previous_epoch, config.SLOTS_PER_EPOCH), config.LATEST_BLOCK_ROOTS_LENGTH, ) previous_epoch_boundary_attester_indices = get_epoch_boundary_attester_indices( state, current_epoch_attestations + previous_epoch_attestations, state.previous_justified_epoch, previous_epoch_boundary_root, CommitteeConfig(config), ) previous_epoch_boundary_attesting_balance = get_total_balance( state.validator_balances, previous_epoch_boundary_attester_indices, config.MAX_DEPOSIT_AMOUNT, ) current_epoch_boundary_root = get_block_root( state, get_epoch_start_slot(current_epoch, config.SLOTS_PER_EPOCH), config.LATEST_BLOCK_ROOTS_LENGTH, ) current_epoch_boundary_attester_indices = get_epoch_boundary_attester_indices( state, current_epoch_attestations, state.justified_epoch, current_epoch_boundary_root, CommitteeConfig(config), ) current_epoch_boundary_attesting_balance = get_total_balance( state.validator_balances, current_epoch_boundary_attester_indices, config.MAX_DEPOSIT_AMOUNT, ) return previous_epoch_boundary_attesting_balance, current_epoch_boundary_attesting_balance
def process_attester_slashings(state: BeaconState, block: BaseBeaconBlock, config: BeaconConfig) -> BeaconState: if len(block.body.attester_slashings) > config.MAX_ATTESTER_SLASHINGS: raise ValidationError( f"The block ({block}) has too many attester slashings:\n" f"\tFound {len(block.body.attester_slashings)} attester slashings, " f"maximum: {config.MAX_ATTESTER_SLASHINGS}") for attester_slashing in block.body.attester_slashings: validate_attester_slashing( state, attester_slashing, config.MAX_INDICES_PER_SLASHABLE_VOTE, config.SLOTS_PER_EPOCH, ) slashable_indices = _get_slashable_indices(state, config, attester_slashing) validate_slashable_indices(slashable_indices) for index in slashable_indices: state = slash_validator( state=state, index=index, latest_slashed_exit_length=config.LATEST_SLASHED_EXIT_LENGTH, whistleblower_reward_quotient=config. WHISTLEBLOWER_REWARD_QUOTIENT, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, committee_config=CommitteeConfig(config), ) return state
def per_block_transition(self, state: BeaconState, block: BaseBeaconBlock, check_proposer_signature: bool=False) -> BeaconState: # TODO: finish per-block processing logic as the spec validate_block_slot(state, block) if not check_proposer_signature: validate_proposer_signature( state, block, beacon_chain_shard_number=self.config.BEACON_CHAIN_SHARD_NUMBER, committee_config=CommitteeConfig(self.config), ) # TODO: state = process_randao(state, block, self.config) state = process_eth1_data(state, block) # Operations # TODO: state = process_proposer_slashings(state, block, self.config) # TODO: state = process_attester_slashings(state, block, self.config) state = process_attestations(state, block, self.config) # TODO: state = process_deposits(state, block, self.config) # TODO: state = process_exits(state, block, self.config) # TODO: validate_custody(state, block, self.config) return state
def per_block_transition( self, state: BeaconState, block: BaseBeaconBlock, check_proposer_signature: bool = True) -> BeaconState: validate_block_slot(state, block) if check_proposer_signature: validate_proposer_signature( state, block, beacon_chain_shard_number=self.config. BEACON_CHAIN_SHARD_NUMBER, committee_config=CommitteeConfig(self.config), ) # TODO: state = process_randao(state, block, self.config) state = process_eth1_data(state, block) # Operations state = process_proposer_slashings(state, block, self.config) state = process_attester_slashings(state, block, self.config) state = process_attestations(state, block, self.config) # TODO: state = process_deposits(state, block, self.config) state = process_voluntary_exits(state, block, self.config) # TODO: state = process_transfers(state, block, self.config) return state
def process_randao(state: BeaconState, block: BaseBeaconBlock, config: BeaconConfig) -> BeaconState: proposer_index = get_beacon_proposer_index( state=state, slot=state.slot, committee_config=CommitteeConfig(config), ) proposer = state.validator_registry[proposer_index] epoch = state.current_epoch(config.SLOTS_PER_EPOCH) validate_randao_reveal( randao_reveal=block.randao_reveal, proposer_pubkey=proposer.pubkey, epoch=epoch, fork=state.fork, ) randao_mix_index = epoch % config.LATEST_RANDAO_MIXES_LENGTH new_randao_mix = bitwise_xor( get_randao_mix( state=state, epoch=epoch, slots_per_epoch=config.SLOTS_PER_EPOCH, latest_randao_mixes_length=config.LATEST_RANDAO_MIXES_LENGTH, ), hash_eth2(block.randao_reveal), ) return state.copy(latest_randao_mixes=update_tuple_item( state.latest_randao_mixes, randao_mix_index, new_randao_mix, ), )
def process_final_updates(state: BeaconState, config: BeaconConfig) -> BeaconState: current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) state = _update_latest_active_index_roots(state, CommitteeConfig(config)) state = state.copy( latest_slashed_balances=update_tuple_item( state.latest_slashed_balances, next_epoch % config.LATEST_SLASHED_EXIT_LENGTH, state.latest_slashed_balances[current_epoch % config.LATEST_SLASHED_EXIT_LENGTH], ), latest_randao_mixes=update_tuple_item( state.latest_randao_mixes, next_epoch % config.LATEST_SLASHED_EXIT_LENGTH, get_randao_mix( state=state, epoch=current_epoch, slots_per_epoch=config.SLOTS_PER_EPOCH, latest_randao_mixes_length=config.LATEST_RANDAO_MIXES_LENGTH, ), ), ) latest_attestations = tuple( filter( lambda attestation: (slot_to_epoch(attestation.data.slot, config.SLOTS_PER_EPOCH) >= current_epoch), state.latest_attestations)) state = state.copy(latest_attestations=latest_attestations, ) return state
def process_attestations(state: BeaconState, block: BaseBeaconBlock, config: BeaconConfig) -> BeaconState: """ Implements 'per-block-processing.operations.attestations' portion of Phase 0 spec: https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestations-1 Validate the ``attestations`` contained within the ``block`` in the context of ``state``. If any invalid, throw ``ValidationError``. Otherwise, append an ``PendingAttestationRecords`` for each to ``latest_attestations``. Return resulting ``state``. """ for attestation in block.body.attestations: validate_attestation( state, attestation, config.MIN_ATTESTATION_INCLUSION_DELAY, config.LATEST_BLOCK_ROOTS_LENGTH, CommitteeConfig(config), ) # update_latest_attestations additional_pending_attestations = tuple( PendingAttestationRecord( data=attestation.data, aggregation_bitfield=attestation.aggregation_bitfield, custody_bitfield=attestation.custody_bitfield, slot_included=state.slot, ) for attestation in block.body.attestations) state = state.copy(latest_attestations=state.latest_attestations + additional_pending_attestations, ) return state
def create_mock_signed_attestations_at_slot( state: BeaconState, config: BeaconConfig, attestation_slot: Slot, beacon_block_root: Hash32, keymap: Dict[BLSPubkey, int], voted_attesters_ratio: float = 1.0) -> Iterable[Attestation]: """ Create the mocking attestations of the given ``attestation_slot`` slot with ``keymap``. """ slots_per_epoch = config.SLOTS_PER_EPOCH crosslink_committees_at_slot = get_crosslink_committees_at_slot( # To avoid the epoch boundary cases state.copy(slot=state.slot + 1, ), attestation_slot, CommitteeConfig(config), ) # Get `epoch_boundary_root` epoch_boundary_root = _get_epoch_boundary_root(state, config, beacon_block_root) # Get `justified_block_root` justified_block_root = get_block_root( state, get_epoch_start_slot(state.justified_epoch, slots_per_epoch), config.LATEST_BLOCK_ROOTS_LENGTH, ) for crosslink_committee in crosslink_committees_at_slot: committee, shard = crosslink_committee num_voted_attesters = int(len(committee) * voted_attesters_ratio) latest_crosslink = state.latest_crosslinks[shard] attestation_data = AttestationData( slot=attestation_slot, shard=shard, beacon_block_root=beacon_block_root, epoch_boundary_root=epoch_boundary_root, crosslink_data_root=ZERO_HASH32, latest_crosslink=latest_crosslink, justified_epoch=state.justified_epoch, justified_block_root=justified_block_root, ) num_voted_attesters = int(len(committee) * voted_attesters_ratio) yield create_mock_signed_attestation( state, attestation_data, committee, num_voted_attesters, keymap, config.SLOTS_PER_EPOCH, )
def test_get_inclusion_infos( monkeypatch, n, n_validators_state, config, slots_per_epoch, target_committee_size, shard_count, attestation_1_inclusion_slot, attestation_1_data_slot, attestation_2_inclusion_slot, attestation_2_data_slot, expected_inclusion_slot, expected_inclusion_distance, sample_attestation_data_params, sample_pending_attestation_record_params): participating_validator_index = 1 committee = (1, 2, 3) shard = 1 from eth2.beacon import committee_helpers def mock_get_crosslink_committees_at_slot(state, slot, committee_config, registry_change=False): return (( committee, shard, ), ) monkeypatch.setattr(committee_helpers, 'get_crosslink_committees_at_slot', mock_get_crosslink_committees_at_slot) aggregation_bitfield = get_empty_bitfield(target_committee_size) aggregation_bitfield = set_voted( aggregation_bitfield, committee.index(participating_validator_index)) previous_epoch_attestations = [ PendingAttestationRecord( **sample_pending_attestation_record_params).copy( data=AttestationData(**sample_attestation_data_params).copy( slot=attestation_1_data_slot, shard=shard, ), aggregation_bitfield=aggregation_bitfield, slot_included=attestation_1_inclusion_slot, ), PendingAttestationRecord( **sample_pending_attestation_record_params).copy( data=AttestationData(**sample_attestation_data_params).copy( slot=attestation_2_data_slot, shard=shard, ), aggregation_bitfield=aggregation_bitfield, slot_included=attestation_2_inclusion_slot, ), ] result = get_inclusion_infos( state=n_validators_state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) assert result[ participating_validator_index].inclusion_slot == expected_inclusion_slot assert result[ participating_validator_index].inclusion_distance == expected_inclusion_distance
def test_validate_proposer_signature( slots_per_epoch, shard_count, proposer_privkey, proposer_pubkey, is_valid_signature, sample_beacon_block_params, sample_beacon_state_params, beacon_chain_shard_number, genesis_epoch, target_committee_size, max_deposit_amount, config): state = BeaconState(**sample_beacon_state_params).copy( validator_registry=tuple( mock_validator_record(proposer_pubkey, config) for _ in range(10)), validator_balances=(max_deposit_amount, ) * 10, ) default_block = BeaconBlock(**sample_beacon_block_params) empty_signature_block_root = default_block.block_without_signature_root proposal_signed_root = Proposal( state.slot, beacon_chain_shard_number, empty_signature_block_root, ).signed_root proposed_block = BeaconBlock(**sample_beacon_block_params).copy( signature=bls.sign( message_hash=proposal_signed_root, privkey=proposer_privkey, domain=SignatureDomain.DOMAIN_PROPOSAL, ), ) if is_valid_signature: validate_proposer_signature( state, proposed_block, beacon_chain_shard_number, CommitteeConfig(config), ) else: with pytest.raises(ValidationError): validate_proposer_signature( state, proposed_block, beacon_chain_shard_number, CommitteeConfig(config), )
def validate_proposer_index(state: BeaconState, config: BeaconConfig, slot: SlotNumber, validator_index: ValidatorIndex): beacon_proposer_index = get_beacon_proposer_index( state.copy(slot=slot, ), slot, CommitteeConfig(config), ) if validator_index != beacon_proposer_index: raise ProposerIndexError
def get_committee_assignment( state: BeaconState, config: BeaconConfig, epoch: Epoch, validator_index: ValidatorIndex, registry_change: bool = False) -> CommitteeAssignment: """ Return the ``CommitteeAssignment`` in the ``epoch`` for ``validator_index`` and ``registry_change``. ``CommitteeAssignment.committee`` is the tuple array of validators in the committee ``CommitteeAssignment.shard`` is the shard to which the committee is assigned ``CommitteeAssignment.slot`` is the slot at which the committee is assigned ``CommitteeAssignment.is_proposer`` is a bool signalling if the validator is expected to propose a beacon block at the assigned slot. """ current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) next_epoch = Epoch(current_epoch + 1) validate_epoch_within_previous_and_next(epoch, previous_epoch, next_epoch) epoch_start_slot = get_epoch_start_slot(epoch, config.SLOTS_PER_EPOCH) committee_config = CommitteeConfig(config) for slot in range(epoch_start_slot, epoch_start_slot + config.SLOTS_PER_EPOCH): crosslink_committees = get_crosslink_committees_at_slot( state, slot, committee_config, registry_change=registry_change, ) selected_committees = [ committee for committee in crosslink_committees if validator_index in committee[0] ] if len(selected_committees) > 0: validators = selected_committees[0][0] shard = selected_committees[0][1] is_proposer = validator_index == get_beacon_proposer_index( state, Slot(slot), committee_config, registry_change=registry_change, ) return CommitteeAssignment(validators, shard, Slot(slot), is_proposer) raise NoCommitteeAssignment
def test_process_proposer_slashings(genesis_state, sample_beacon_block_params, sample_beacon_block_body_params, config, keymap, block_root_1, block_root_2, success): current_slot = config.GENESIS_SLOT + 1 state = genesis_state.copy(slot=current_slot, ) whistleblower_index = get_beacon_proposer_index( state, state.slot, CommitteeConfig(config), ) slashing_proposer_index = (whistleblower_index + 1) % len( state.validator_registry) proposer_slashing = create_mock_proposer_slashing_at_block( state, config, keymap, block_root_1=block_root_1, block_root_2=block_root_2, proposer_index=slashing_proposer_index, ) proposer_slashings = (proposer_slashing, ) block_body = BeaconBlockBody(**sample_beacon_block_body_params).copy( proposer_slashings=proposer_slashings, ) block = SerenityBeaconBlock(**sample_beacon_block_params).copy( slot=current_slot, body=block_body, ) if success: new_state = process_proposer_slashings( state, block, config, ) # Check if slashed assert (new_state.validator_balances[slashing_proposer_index] < state.validator_balances[slashing_proposer_index]) else: with pytest.raises(ValidationError): process_proposer_slashings( state, block, config, )
def create_mock_signed_attestations_at_slot( state: BeaconState, config: BeaconConfig, attestation_slot: SlotNumber, keymap: Dict[BLSPubkey, int], voted_attesters_ratio: float = 1.0) -> Iterable[Attestation]: """ Create the mocking attestations of the given ``attestation_slot`` slot with ``keymap``. """ crosslink_committees_at_slot = get_crosslink_committees_at_slot( state.copy(slot=state.slot + 1, ), attestation_slot, CommitteeConfig(config), ) for crosslink_committee in crosslink_committees_at_slot: committee, shard = crosslink_committee num_voted_attesters = int(len(committee) * voted_attesters_ratio) latest_crosslink_root = state.latest_crosslinks[shard].shard_block_root attestation_data = AttestationData( slot=attestation_slot, shard=shard, beacon_block_root=ZERO_HASH32, epoch_boundary_root=ZERO_HASH32, shard_block_root=ZERO_HASH32, latest_crosslink_root=latest_crosslink_root, justified_epoch=state.previous_justified_epoch, justified_block_root=get_block_root( state, get_epoch_start_slot(state.previous_justified_epoch, config.EPOCH_LENGTH), config.LATEST_BLOCK_ROOTS_LENGTH, ), ) yield create_mock_signed_attestation( state, attestation_data, committee, num_voted_attesters, keymap, config.EPOCH_LENGTH, )
def _process_rewards_and_penalties_for_attestation_inclusion( state: BeaconState, config: BeaconConfig, previous_epoch_attester_indices: Set[ValidatorIndex], inclusion_infos: Dict[ValidatorIndex, InclusionInfo], base_rewards: Dict[ValidatorIndex, Gwei], old_rewards_received: Dict[ValidatorIndex, SignedGwei] ) -> Dict[ValidatorIndex, SignedGwei]: rewards_received = old_rewards_received.copy() for index in previous_epoch_attester_indices: proposer_index = get_beacon_proposer_index( state, inclusion_infos[index].inclusion_slot, CommitteeConfig(config), ) reward = base_rewards[ index] // config.ATTESTATION_INCLUSION_REWARD_QUOTIENT rewards_received[proposer_index] = SignedGwei( rewards_received[proposer_index] + reward) return rewards_received
def get_next_epoch_committee_assignment( state: BeaconState, config: BeaconConfig, validator_index: ValidatorIndex, registry_change: bool) -> CommitteeAssignment: """ Return the ``CommitteeAssignment`` in the next epoch for ``validator_index`` and ``registry_change``. ``CommitteeAssignment.committee`` is the tuple array of validators in the committee ``CommitteeAssignment.shard`` is the shard to which the committee is assigned ``CommitteeAssignment.slot`` is the slot at which the committee is assigned ``CommitteeAssignment.is_proposer`` is a bool signalling if the validator is expected to propose a beacon block at the assigned slot. """ current_epoch = state.current_epoch(config.EPOCH_LENGTH) next_epoch = current_epoch + 1 next_epoch_start_slot = get_epoch_start_slot(next_epoch, config.EPOCH_LENGTH) for slot in range(next_epoch_start_slot, next_epoch_start_slot + config.EPOCH_LENGTH): crosslink_committees = get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), registry_change=registry_change, ) selected_committees = [ committee for committee in crosslink_committees if validator_index in committee[0] ] if len(selected_committees) > 0: validators = selected_committees[0][0] shard = selected_committees[0][1] first_committee_at_slot = crosslink_committees[0][ 0] # List[ValidatorIndex] is_proposer = first_committee_at_slot[ slot % len(first_committee_at_slot)] == validator_index return CommitteeAssignment(validators, shard, slot, is_proposer) raise NoCommitteeAssignment
def process_proposer_slashings(state: BeaconState, block: BaseBeaconBlock, config: BeaconConfig) -> BeaconState: if len(block.body.proposer_slashings) > config.MAX_PROPOSER_SLASHINGS: raise ValidationError( f"The block ({block}) has too many proposer slashings:\n" f"\tFound {len(block.body.proposer_slashings)} proposer slashings, " f"maximum: {config.MAX_PROPOSER_SLASHINGS}") for proposer_slashing in block.body.proposer_slashings: validate_proposer_slashing(state, proposer_slashing, config.SLOTS_PER_EPOCH) state = slash_validator( state=state, index=proposer_slashing.proposer_index, latest_slashed_exit_length=config.LATEST_SLASHED_EXIT_LENGTH, whistleblower_reward_quotient=config.WHISTLEBLOWER_REWARD_QUOTIENT, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, committee_config=CommitteeConfig(config), ) return state
def create_mock_block( *, state: BeaconState, config: BeaconConfig, state_machine: BaseBeaconStateMachine, block_class: Type[BaseBeaconBlock], parent_block: BaseBeaconBlock, keymap: Dict[BLSPubkey, int], slot: Slot = None, attestations: Sequence[Attestation] = () ) -> BaseBeaconBlock: """ Create a mocking block with the given block parameters and ``keymap``. Note that it doesn't return the correct ``state_root``. """ proposer_index = get_beacon_proposer_index( state.copy(slot=slot, ), slot, CommitteeConfig(config), ) proposer_pubkey = state.validator_registry[proposer_index].pubkey proposer_privkey = keymap[proposer_pubkey] result_block = create_block_on_state( state=state, config=config, state_machine=state_machine, block_class=block_class, parent_block=parent_block, slot=slot, validator_index=proposer_index, privkey=proposer_privkey, attestations=attestations, check_proposer_index=False, ) return result_block
def _process_rewards_and_penalties_for_crosslinks( state: BeaconState, config: BeaconConfig, previous_epoch_attestations: Iterable[PendingAttestationRecord], effective_balances: Dict[ValidatorIndex, Gwei], base_rewards: Dict[ValidatorIndex, Gwei], old_rewards_received: Dict[ValidatorIndex, SignedGwei] ) -> Dict[ValidatorIndex, SignedGwei]: rewards_received = old_rewards_received.copy() previous_epoch_start_slot = get_epoch_start_slot( state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH), config.SLOTS_PER_EPOCH, ) current_epoch_start_slot = get_epoch_start_slot( state.current_epoch(config.SLOTS_PER_EPOCH), config.SLOTS_PER_EPOCH, ) # Also need current epoch attestations to compute the winning root. current_epoch_attestations = get_current_epoch_attestations( state, config.SLOTS_PER_EPOCH) 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: filtered_attestations = _filter_attestations_by_shard( previous_epoch_attestations + current_epoch_attestations, shard, ) try: winning_root, total_attesting_balance = get_winning_root( state=state, shard=shard, attestations=filtered_attestations, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, committee_config=CommitteeConfig(config), ) except NoWinningRootError: # No winning shard block root found for this shard. # Hence no one is counted as attesting validator. attesting_validator_indices: Iterable[ValidatorIndex] = set() else: attesting_validator_indices = get_attester_indices_from_attesttion( state=state, attestations=(a for a in filtered_attestations if a.data.shard == shard and a.data.crosslink_data_root == winning_root), committee_config=CommitteeConfig(config), ) total_balance = get_total_balance_from_effective_balances( effective_balances, crosslink_committee, ) for index in attesting_validator_indices: reward = base_rewards[ index] * total_attesting_balance // total_balance rewards_received[index] = SignedGwei(rewards_received[index] + reward) for index in set(crosslink_committee).difference( attesting_validator_indices): penalty = base_rewards[index] rewards_received[index] = SignedGwei(rewards_received[index] - penalty) return rewards_received
def test_process_crosslinks( random, n_validators_state, config, slots_per_epoch, target_committee_size, shard_count, success_crosslink_in_cur_epoch, sample_attestation_data_params, sample_attestation_params): shard = 1 crosslink_data_root = hash_eth2(b'crosslink_data_root') current_slot = config.SLOTS_PER_EPOCH * 2 - 1 genesis_crosslinks = tuple([ CrosslinkRecord(epoch=config.GENESIS_EPOCH, crosslink_data_root=ZERO_HASH32) for _ in range(shard_count) ]) state = n_validators_state.copy( slot=current_slot, latest_crosslinks=genesis_crosslinks, ) # Generate current epoch attestations cur_epoch_attestations = [] for slot_in_cur_epoch in range(state.slot - config.SLOTS_PER_EPOCH, state.slot): if len(cur_epoch_attestations) > 0: break for committee, _shard in get_crosslink_committees_at_slot( state, slot_in_cur_epoch, CommitteeConfig(config), ): if _shard == shard: # Sample validators attesting to this shard. # Number of attesting validators sampled depends on `success_crosslink_in_cur_epoch` # if True, have >2/3 committee attest if success_crosslink_in_cur_epoch: attesting_validators = random.sample(committee, (2 * len(committee) // 3 + 1)) else: attesting_validators = random.sample(committee, (2 * len(committee) // 3 - 1)) # Generate the bitfield aggregation_bitfield = get_empty_bitfield(len(committee)) for v_index in attesting_validators: aggregation_bitfield = set_voted( aggregation_bitfield, committee.index(v_index)) # Generate the attestation cur_epoch_attestations.append( Attestation(**sample_attestation_params).copy( aggregation_bitfield=aggregation_bitfield, data=AttestationData(**sample_attestation_data_params).copy( slot=slot_in_cur_epoch, shard=shard, crosslink_data_root=crosslink_data_root, ), ) ) state = state.copy( latest_attestations=cur_epoch_attestations, ) assert (state.latest_crosslinks[shard].epoch == config.GENESIS_EPOCH and state.latest_crosslinks[shard].crosslink_data_root == ZERO_HASH32) new_state = process_crosslinks(state, config) crosslink_record = new_state.latest_crosslinks[shard] if success_crosslink_in_cur_epoch: attestation = cur_epoch_attestations[0] assert (crosslink_record.epoch == slot_to_epoch(current_slot, slots_per_epoch) and crosslink_record.crosslink_data_root == attestation.data.crosslink_data_root and attestation.data.crosslink_data_root == crosslink_data_root) else: assert (crosslink_record.epoch == config.GENESIS_EPOCH and crosslink_record.crosslink_data_root == ZERO_HASH32)
def test_process_rewards_and_penalties_for_finality( n_validators_state, config, slots_per_epoch, target_committee_size, shard_count, min_attestation_inclusion_delay, inactivity_penalty_quotient, finalized_epoch, current_slot, penalized_validator_indices, previous_epoch_active_validator_indices, previous_epoch_attester_indices, previous_epoch_boundary_head_attester_indices, inclusion_distances, effective_balance, base_reward, expected_rewards_received, sample_pending_attestation_record_params, sample_attestation_data_params): validator_registry = n_validators_state.validator_registry for index in penalized_validator_indices: validator_record = validator_registry[index].copy( slashed=True, ) validator_registry = update_tuple_item(validator_registry, index, validator_record) state = n_validators_state.copy( slot=current_slot, finalized_epoch=finalized_epoch, validator_registry=validator_registry, ) previous_total_balance = len(previous_epoch_active_validator_indices) * effective_balance attestation_slot = current_slot - slots_per_epoch inclusion_infos = { index: InclusionInfo( attestation_slot + inclusion_distances[index], attestation_slot, ) for index in previous_epoch_attester_indices } effective_balances = { index: effective_balance for index in previous_epoch_active_validator_indices } base_rewards = { index: base_reward for index in previous_epoch_active_validator_indices } rewards_received = { index: 0 for index in range(len(state.validator_registry)) } prev_epoch_start_slot = get_epoch_start_slot( state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH), slots_per_epoch, ) prev_epoch_crosslink_committees = [ get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), )[0] for slot in range(prev_epoch_start_slot, prev_epoch_start_slot + slots_per_epoch) ] prev_epoch_attestations = [] for i in range(slots_per_epoch): committee, shard = prev_epoch_crosslink_committees[i] participants_bitfield = get_empty_bitfield(target_committee_size) for index in previous_epoch_boundary_head_attester_indices: if index in committee: participants_bitfield = set_voted(participants_bitfield, committee.index(index)) prev_epoch_attestations.append( PendingAttestationRecord(**sample_pending_attestation_record_params).copy( data=AttestationData(**sample_attestation_data_params).copy( slot=(prev_epoch_start_slot + i), shard=shard, epoch_boundary_root=get_block_root( state, prev_epoch_start_slot, config.LATEST_BLOCK_ROOTS_LENGTH, ), beacon_block_root=get_block_root( state, (prev_epoch_start_slot + i), config.LATEST_BLOCK_ROOTS_LENGTH, ), ), aggregation_bitfield=participants_bitfield, ) ) state = state.copy( latest_attestations=prev_epoch_attestations, ) rewards_received = _process_rewards_and_penalties_for_finality( state, config, previous_epoch_active_validator_indices, previous_total_balance, prev_epoch_attestations, previous_epoch_attester_indices, inclusion_infos, effective_balances, base_rewards, rewards_received, ) for index, reward_received in rewards_received.items(): assert reward_received == expected_rewards_received[index]
def test_process_rewards_and_penalties_for_crosslinks( random, n_validators_state, config, slots_per_epoch, target_committee_size, shard_count, current_slot, num_attesting_validators, max_deposit_amount, min_attestation_inclusion_delay, sample_attestation_data_params, sample_pending_attestation_record_params): previous_epoch = current_slot // slots_per_epoch - 1 state = n_validators_state.copy( slot=current_slot, ) # Compute previous epoch committees prev_epoch_start_slot = get_epoch_start_slot(previous_epoch, slots_per_epoch) prev_epoch_crosslink_committees = [ get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), )[0] for slot in range(prev_epoch_start_slot, prev_epoch_start_slot + slots_per_epoch) ] # Record which validators attest during each slot for reward collation. each_slot_attestion_validators_list = [] previous_epoch_attestations = [] for i in range(slots_per_epoch): committee, shard = prev_epoch_crosslink_committees[i] # Randomly sample `num_attesting_validators` validators # from the committee to attest in this slot. crosslink_data_root_attesting_validators = random.sample( committee, num_attesting_validators, ) each_slot_attestion_validators_list.append(crosslink_data_root_attesting_validators) participants_bitfield = get_empty_bitfield(target_committee_size) for index in crosslink_data_root_attesting_validators: participants_bitfield = set_voted(participants_bitfield, committee.index(index)) data_slot = i + previous_epoch * slots_per_epoch previous_epoch_attestations.append( PendingAttestationRecord(**sample_pending_attestation_record_params).copy( data=AttestationData(**sample_attestation_data_params).copy( slot=data_slot, shard=shard, ), aggregation_bitfield=participants_bitfield, slot_included=(data_slot + min_attestation_inclusion_delay), ) ) active_validators = set( [ i for i in range(len(state.validator_registry)) ] ) effective_balances = { index: get_effective_balance( state.validator_balances, index, config.MAX_DEPOSIT_AMOUNT, ) for index in active_validators } validator_balance = max_deposit_amount total_active_balance = len(active_validators) * validator_balance _base_reward_quotient = ( integer_squareroot(total_active_balance) // config.BASE_REWARD_QUOTIENT ) base_rewards = { index: get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=max_deposit_amount, ) for index in active_validators } rewards_received = { index: 0 for index in range(len(state.validator_registry)) } rewards_received = _process_rewards_and_penalties_for_crosslinks( state, config, tuple(previous_epoch_attestations), effective_balances, base_rewards, rewards_received, ) expected_rewards_received = { index: 0 for index in range(len(state.validator_registry)) } for i in range(slots_per_epoch): crosslink_committee, shard = prev_epoch_crosslink_committees[i] attesting_validators = each_slot_attestion_validators_list[i] total_attesting_balance = len(attesting_validators) * validator_balance total_committee_balance = len(crosslink_committee) * validator_balance _base_reward_quotient = ( integer_squareroot(total_active_balance) // config.BASE_REWARD_QUOTIENT ) for index in attesting_validators: reward = get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=max_deposit_amount, ) * total_attesting_balance // total_committee_balance expected_rewards_received[index] += reward for index in set(crosslink_committee).difference(attesting_validators): penalty = get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=max_deposit_amount, ) expected_rewards_received[index] -= penalty # Check the rewards/penalties match for index, reward_received in rewards_received.items(): assert rewards_received[index] == expected_rewards_received[index]
def process_crosslinks(state: BeaconState, config: BeaconConfig) -> BeaconState: """ Implement 'per-epoch-processing.crosslinks' portion of Phase 0 spec: https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#crosslinks For each shard from the past two epochs, find the shard block root that has been attested to by the most stake. If enough(>= 2/3 total stake) attesting stake, update the crosslink record of that shard. Return resulting ``state`` """ latest_crosslinks = state.latest_crosslinks previous_epoch_attestations = get_previous_epoch_attestations( state, config.EPOCH_LENGTH, config.GENESIS_EPOCH, ) current_epoch_attestations = get_current_epoch_attestations(state, config.EPOCH_LENGTH) prev_epoch_start_slot = get_epoch_start_slot( state.previous_epoch(config.EPOCH_LENGTH, config.GENESIS_EPOCH), config.EPOCH_LENGTH, ) next_epoch_start_slot = get_epoch_start_slot( state.next_epoch(config.EPOCH_LENGTH), config.EPOCH_LENGTH, ) for slot in range(prev_epoch_start_slot, next_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: try: winning_root, total_attesting_balance = get_winning_root( state=state, shard=shard, # Use `_filter_attestations_by_shard` to filter out attestations # not attesting to this shard so we don't need to going over # irrelevent attestations over and over again. attestations=_filter_attestations_by_shard( previous_epoch_attestations + current_epoch_attestations, shard, ), max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, committee_config=CommitteeConfig(config), ) except NoWinningRootError: # No winning shard block root found for this shard. pass else: total_balance = sum( get_effective_balance(state.validator_balances, i, config.MAX_DEPOSIT_AMOUNT) for i in crosslink_committee ) if 3 * total_attesting_balance >= 2 * total_balance: latest_crosslinks = update_tuple_item( latest_crosslinks, shard, CrosslinkRecord( epoch=state.current_epoch(config.EPOCH_LENGTH), shard_block_root=winning_root, ), ) else: # Don't update the crosslink of this shard pass state = state.copy( latest_crosslinks=latest_crosslinks, ) return state
def _process_rewards_and_penalties_for_finality( state: BeaconState, config: BeaconConfig, previous_epoch_active_validator_indices: Set[ValidatorIndex], previous_total_balance: Gwei, previous_epoch_attestations: Sequence[Attestation], previous_epoch_attester_indices: Set[ValidatorIndex], inclusion_infos: Dict[ValidatorIndex, InclusionInfo], effective_balances: Dict[ValidatorIndex, Gwei], base_rewards: Dict[ValidatorIndex, Gwei], old_rewards_received: Dict[ValidatorIndex, SignedGwei] ) -> Dict[ValidatorIndex, SignedGwei]: previous_epoch_boundary_attestations = ( a for a in previous_epoch_attestations if a.data.epoch_boundary_root == get_block_root( state, get_epoch_start_slot( state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH), config.SLOTS_PER_EPOCH, ), config.LATEST_BLOCK_ROOTS_LENGTH, )) previous_epoch_boundary_attester_indices = get_attester_indices_from_attesttion( state=state, attestations=previous_epoch_boundary_attestations, committee_config=CommitteeConfig(config), ) previous_epoch_head_attestations = get_previous_epoch_head_attestations( state, config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH, config.LATEST_BLOCK_ROOTS_LENGTH, ) previous_epoch_head_attester_indices = get_attester_indices_from_attesttion( state=state, attestations=previous_epoch_head_attestations, committee_config=CommitteeConfig(config), ) rewards_received = {index: Gwei(0) for index in old_rewards_received} penalties_received = rewards_received.copy() epochs_since_finality = state.next_epoch( config.SLOTS_PER_EPOCH) - state.finalized_epoch if epochs_since_finality <= 4: # 1.1 Expected FFG source: previous_epoch_attesting_balance = get_total_balance_from_effective_balances( effective_balances, previous_epoch_attester_indices, ) # Reward validators in `previous_epoch_attester_indices` # # Punish active validators not in `previous_epoch_attester_indices` excluded_active_validators_indices = previous_epoch_active_validator_indices.difference( previous_epoch_attester_indices, ) rewards = { index: Gwei(base_rewards[index] * previous_epoch_attesting_balance // previous_total_balance) for index in previous_epoch_attester_indices } penalties = { index: base_rewards[index] for index in excluded_active_validators_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( rewards=rewards, indices_to_reward=previous_epoch_attester_indices, penalties=penalties, indices_to_penalize=excluded_active_validators_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # 1.2 Expected FFG target: previous_epoch_boundary_attesting_balance = get_total_balance_from_effective_balances( effective_balances, previous_epoch_boundary_attester_indices, ) # Reward validators in `previous_epoch_boundary_attester_indices` # Punish active validators not in `previous_epoch_boundary_attester_indices` excluded_active_validators_indices = previous_epoch_active_validator_indices.difference( previous_epoch_boundary_attester_indices, ) rewards = { index: Gwei(base_rewards[index] * previous_epoch_boundary_attesting_balance // previous_total_balance) for index in previous_epoch_boundary_attester_indices } penalties = { index: base_rewards[index] for index in excluded_active_validators_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( rewards=rewards, indices_to_reward=previous_epoch_boundary_attester_indices, penalties=penalties, indices_to_penalize=excluded_active_validators_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # 1.3 Expected beacon chain head: previous_epoch_head_attesting_balance = get_total_balance_from_effective_balances( effective_balances, previous_epoch_head_attester_indices, ) # Reward validators in `previous_epoch_head_attester_indices` # Punish active validators not in `previous_epoch_head_attester_indices` excluded_active_validators_indices = previous_epoch_active_validator_indices.difference( previous_epoch_head_attester_indices, ) rewards = { index: Gwei(base_rewards[index] * previous_epoch_head_attesting_balance // previous_total_balance) for index in previous_epoch_head_attester_indices } penalties = { index: base_rewards[index] for index in excluded_active_validators_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( rewards=rewards, indices_to_reward=previous_epoch_head_attester_indices, penalties=penalties, indices_to_penalize=excluded_active_validators_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # 1.4 Inclusion distance: # Reward validators in `previous_epoch_attester_indices` rewards = { index: Gwei(base_rewards[index] * config.MIN_ATTESTATION_INCLUSION_DELAY // inclusion_infos[index].inclusion_distance) for index in previous_epoch_attester_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( rewards=rewards, indices_to_reward=previous_epoch_attester_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # epochs_since_finality > 4 else: # Punish active validators not in `previous_epoch_attester_indices` excluded_active_validators_indices = previous_epoch_active_validator_indices.difference( previous_epoch_attester_indices, ) inactivity_penalties = { index: base_rewards[index] + (effective_balances[index] * epochs_since_finality // config.INACTIVITY_PENALTY_QUOTIENT // 2) for index in excluded_active_validators_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( penalties=inactivity_penalties, indices_to_penalize=excluded_active_validators_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # Punish active validators not in `previous_epoch_boundary_attester_indices` excluded_active_validators_indices = previous_epoch_active_validator_indices.difference( previous_epoch_boundary_attester_indices, ) inactivity_penalties = { index: base_rewards[index] + (effective_balances[index] * epochs_since_finality // config.INACTIVITY_PENALTY_QUOTIENT // 2) for index in excluded_active_validators_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( penalties=inactivity_penalties, indices_to_penalize=excluded_active_validators_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # Punish active validators not in `previous_epoch_head_attester_indices` excluded_active_validators_indices = previous_epoch_active_validator_indices.difference( previous_epoch_head_attester_indices, ) penalties = { index: base_rewards[index] for index in excluded_active_validators_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( penalties=penalties, indices_to_penalize=excluded_active_validators_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # Punish penalized active validators penalties = { index: 3 * base_rewards[index] + 2 * (effective_balances[index] * epochs_since_finality // config.INACTIVITY_PENALTY_QUOTIENT // 2) for index in previous_epoch_active_validator_indices if state.validator_registry[index].slashed is True } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( penalties=penalties, indices_to_penalize={index for index in penalties}, rewards_received=rewards_received, penalties_received=penalties_received, ), ) # Punish validators in `previous_epoch_attester_indices` penalties = { index: Gwei(base_rewards[index] - (base_rewards[index] * config.MIN_ATTESTATION_INCLUSION_DELAY // inclusion_infos[index].inclusion_distance)) for index in previous_epoch_attester_indices } rewards_received, penalties_received = _apply_rewards_and_penalties( RewardSettlementContext( penalties=penalties, indices_to_penalize=previous_epoch_attester_indices, rewards_received=rewards_received, penalties_received=penalties_received, ), ) historical_rewards_received = old_rewards_received.copy() for index in rewards_received: historical_rewards_received = _update_rewards_or_penalies( index, rewards_received[index] - penalties_received[index], historical_rewards_received, ) return historical_rewards_received
def committee_config(config): return CommitteeConfig(config)
def process_rewards_and_penalties(state: BeaconState, config: BeaconConfig) -> BeaconState: # Compute previous epoch active validator indices and the total balance they account for # for later use. previous_epoch_active_validator_indices = set( get_active_validator_indices( state.validator_registry, state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH))) previous_total_balance: Gwei = get_total_balance( state.validator_balances, tuple(previous_epoch_active_validator_indices), config.MAX_DEPOSIT_AMOUNT, ) # Compute previous epoch attester indices and the total balance they account for # for later use. previous_epoch_attestations = get_previous_epoch_attestations( state, config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH, ) previous_epoch_attester_indices = get_attester_indices_from_attesttion( state=state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) # Compute inclusion slot/distance of previous attestations for later use. inclusion_infos = get_inclusion_infos( state=state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) # Compute effective balance of each previous epoch active validator for later use effective_balances = { index: get_effective_balance( state.validator_balances, index, config.MAX_DEPOSIT_AMOUNT, ) for index in previous_epoch_active_validator_indices } # Compute base reward of each previous epoch active validator for later use _base_reward_quotient = (integer_squareroot(previous_total_balance) // config.BASE_REWARD_QUOTIENT) base_rewards = { index: get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, ) for index in previous_epoch_active_validator_indices } # Initialize the reward (validator) received map rewards_received = { index: SignedGwei(0) for index in previous_epoch_active_validator_indices } # 1. Process rewards and penalties for justification and finalization rewards_received = pipe( rewards_received, _process_rewards_and_penalties_for_finality( state, config, previous_epoch_active_validator_indices, previous_total_balance, previous_epoch_attestations, previous_epoch_attester_indices, inclusion_infos, effective_balances, base_rewards, ), _process_rewards_and_penalties_for_attestation_inclusion( state, config, previous_epoch_attester_indices, inclusion_infos, base_rewards, ), _process_rewards_and_penalties_for_crosslinks( state, config, previous_epoch_attestations, effective_balances, base_rewards, )) # Apply the overall rewards/penalties for index in previous_epoch_active_validator_indices: state = state.update_validator_balance( index, # Prevent validator balance under flow max(state.validator_balances[index] + rewards_received[index], 0), ) return state