def test_get_epoch_start_shard(genesis_state, current_epoch, target_epoch, expected_epoch_start_shard, config): state = genesis_state.copy( slot=get_epoch_start_slot(current_epoch, config.SLOTS_PER_EPOCH), ) if expected_epoch_start_shard is None: with pytest.raises(ValidationError): get_epoch_start_shard(state, target_epoch, CommitteeConfig(config)) else: epoch_start_shard = get_epoch_start_shard(state, target_epoch, CommitteeConfig(config)) assert epoch_start_shard == expected_epoch_start_shard
def test_calculate_first_committee_at_slot(genesis_state, config): state = genesis_state slots_per_epoch = config.SLOTS_PER_EPOCH shard_count = config.SHARD_COUNT target_committee_size = config.TARGET_COMMITTEE_SIZE current_epoch = state.current_epoch(slots_per_epoch) active_validator_indices = get_active_validator_indices(state.validators, current_epoch) committees_per_slot = get_committees_per_slot( len(active_validator_indices), shard_count, slots_per_epoch, target_committee_size, ) assert state.slot % config.SLOTS_PER_EPOCH == 0 for slot in range(state.slot, state.slot + config.SLOTS_PER_EPOCH): offset = committees_per_slot * (slot % slots_per_epoch) shard = ( get_epoch_start_shard(state, current_epoch, config) + offset ) % shard_count committee = get_crosslink_committee( state, current_epoch, shard, config, ) assert committee == _calculate_first_committee_at_slot(state, slot, CommitteeConfig(config))
def process_crosslinks(state: BeaconState, config: Eth2Config) -> BeaconState: current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) new_current_crosslinks = state.current_crosslinks for epoch in (previous_epoch, current_epoch): active_validators_indices = get_active_validator_indices(state.validators, epoch) epoch_committee_count = get_epoch_committee_count( len(active_validators_indices), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) epoch_start_shard = get_epoch_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 = get_crosslink_committee( state, epoch, shard, CommitteeConfig(config), ) if not crosslink_committee: # empty crosslink committee this epoch continue winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices( state=state, epoch=epoch, shard=shard, config=config, ) threshold_met = _is_threshold_met_against_committee( state, attesting_indices, crosslink_committee, ) if threshold_met: new_current_crosslinks = update_tuple_item( new_current_crosslinks, shard, winning_crosslink, ) return state.copy( previous_crosslinks=state.current_crosslinks, current_crosslinks=new_current_crosslinks, )
def _mk_attestation_inputs_in_epoch(epoch, state, config): active_validators_indices = get_active_validator_indices( state.validators, epoch) epoch_committee_count = get_epoch_committee_count( len(active_validators_indices), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) epoch_start_shard = get_epoch_start_shard( state, epoch, CommitteeConfig(config), ) for shard_offset in random.sample(range(epoch_committee_count), epoch_committee_count): shard = Shard((epoch_start_shard + shard_offset) % config.SHARD_COUNT) committee = get_crosslink_committee( state, epoch, shard, CommitteeConfig(config), ) if not committee: # empty crosslink committee this epoch continue attestation_data = AttestationData( target_epoch=epoch, crosslink=Crosslink(shard=shard, ), ) committee_count = len(committee) aggregation_bitfield = bitfield.get_empty_bitfield(committee_count) for index in range(committee_count): aggregation_bitfield = bitfield.set_voted(aggregation_bitfield, index) for index in committee: yield ( index, ( get_attestation_data_slot( state, attestation_data, config, ), ( aggregation_bitfield, attestation_data, ), ), )
def get_committee_assignment(state: BeaconState, config: Eth2Config, epoch: Epoch, validator_index: ValidatorIndex) -> CommitteeAssignment: """ Return the ``CommitteeAssignment`` in the ``epoch`` for ``validator_index``. ``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. """ next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) if epoch > next_epoch: raise ValidationError( f"Epoch for committee assignment ({epoch}) must not be after next epoch {next_epoch}." ) active_validators = get_active_validator_indices(state.validators, epoch) committees_per_slot = get_epoch_committee_count( len(active_validators), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) // config.SLOTS_PER_EPOCH epoch_start_slot = get_epoch_start_slot( epoch, config.SLOTS_PER_EPOCH, ) epoch_start_shard = get_epoch_start_shard(state, epoch, CommitteeConfig(config)) for slot in range(epoch_start_slot, epoch_start_slot + config.SLOTS_PER_EPOCH): offset = committees_per_slot * (slot % config.SLOTS_PER_EPOCH) slot_start_shard = (epoch_start_shard + offset) % config.SHARD_COUNT for i in range(committees_per_slot): shard = Shard((slot_start_shard + i) % config.SHARD_COUNT) committee = get_crosslink_committee(state, epoch, shard, CommitteeConfig(config)) if validator_index in committee: is_proposer = validator_index == get_beacon_proposer_index( state.copy( slot=slot, ), CommitteeConfig(config), ) return CommitteeAssignment(committee, Shard(shard), Slot(slot), is_proposer) raise NoCommitteeAssignment
def _find_collision(state, config, index, epoch): """ Given a target epoch, make the attestation expected for the validator w/ the given index. """ active_validators = get_active_validator_indices(state.validators, epoch) committees_per_slot = get_epoch_committee_count( len(active_validators), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) // config.SLOTS_PER_EPOCH epoch_start_slot = get_epoch_start_slot( epoch, config.SLOTS_PER_EPOCH, ) epoch_start_shard = get_epoch_start_shard(state, epoch, CommitteeConfig(config)) for slot in range(epoch_start_slot, epoch_start_slot + config.SLOTS_PER_EPOCH): offset = committees_per_slot * (slot % config.SLOTS_PER_EPOCH) slot_start_shard = (epoch_start_shard + offset) % config.SHARD_COUNT for i in range(committees_per_slot): shard = Shard((slot_start_shard + i) % config.SHARD_COUNT) committee = get_crosslink_committee(state, epoch, shard, CommitteeConfig(config)) if index in committee: # TODO(ralexstokes) refactor w/ tools/builder attestation_data = AttestationData( target_epoch=epoch, crosslink=Crosslink(shard=shard, ), ) committee_count = len(committee) aggregation_bitfield = bitfield.get_empty_bitfield( committee_count) for i in range(committee_count): aggregation_bitfield = bitfield.set_voted( aggregation_bitfield, i) return { index: (slot, (aggregation_bitfield, attestation_data)) for index in committee } else: raise Exception("should have found a duplicate validator")
def _mk_some_pending_attestations_with_some_participation_in_epoch( state: BeaconState, epoch: Epoch, config: Eth2Config, participation_ratio: float, number_of_shards_to_check: int) -> Iterable[PendingAttestation]: block_root = get_block_root( state, epoch, config.SLOTS_PER_EPOCH, config.SLOTS_PER_HISTORICAL_ROOT, ) epoch_start_shard = get_epoch_start_shard( state, epoch, CommitteeConfig(config), ) if epoch == state.current_epoch(config.SLOTS_PER_EPOCH): parent_crosslinks = state.current_crosslinks else: parent_crosslinks = state.previous_crosslinks for shard in range(epoch_start_shard, epoch_start_shard + number_of_shards_to_check): shard = Shard(shard % config.SHARD_COUNT) crosslink_committee = get_crosslink_committee( state, epoch, shard, CommitteeConfig(config), ) if not crosslink_committee: continue participants_count = math.ceil(participation_ratio * len(crosslink_committee)) if not participants_count: return tuple() yield mk_pending_attestation_from_committee( parent_crosslinks[shard], participants_count, shard, target_epoch=epoch, target_root=block_root, )
def get_crosslink_committees_at_slot( state: BeaconState, slot: Slot, config: Eth2Config ) -> Tuple[Tuple[Tuple[ValidatorIndex, ...], Shard], ...]: epoch = slot_to_epoch(slot, config.SLOTS_PER_EPOCH) active_validators = get_active_validator_indices(state.validators, epoch) committees_per_slot = get_epoch_committee_count( len(active_validators), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) // config.SLOTS_PER_EPOCH results = [] offset = committees_per_slot * (slot % config.SLOTS_PER_EPOCH) slot_start_shard = Shard( (get_epoch_start_shard(state, epoch, CommitteeConfig(config)) + offset) % config.SHARD_COUNT) for i in range(committees_per_slot): shard = (slot_start_shard + i) % config.SHARD_COUNT committee = get_crosslink_committee(state, epoch, shard, CommitteeConfig(config)) results.append((committee, Shard(shard))) return tuple(results)
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_epoch_committee_count( len(active_validators_indices), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) epoch_start_shard = get_epoch_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 = 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 test_process_rewards_and_penalties_for_crosslinks( genesis_state, config, slots_per_epoch, target_committee_size, shard_count, current_slot, num_attesting_validators, max_effective_balance, min_attestation_inclusion_delay, sample_attestation_data_params, sample_pending_attestation_record_params): state = genesis_state.copy(slot=current_slot, ) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) 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 = [] epoch_start_shard = get_epoch_start_shard( state, previous_epoch, CommitteeConfig(config), ) shard_delta = get_shard_delta( state, previous_epoch, CommitteeConfig(config), ) a = epoch_start_shard b = epoch_start_shard + shard_delta if a > b: valid_shards_for_epoch = range(b, a) else: valid_shards_for_epoch = range(a, b) indices_to_check = set() previous_epoch_attestations = [] for committee, shard in prev_epoch_crosslink_committees: if shard not in valid_shards_for_epoch: continue for index in committee: indices_to_check.add(index) # Randomly sample `num_attesting_validators` validators # from the committee to attest in this slot. crosslink_attesting_validators = random.sample( committee, num_attesting_validators, ) each_slot_attestion_validators_list.append( crosslink_attesting_validators) participants_bitfield = get_empty_bitfield(len(committee)) for index in crosslink_attesting_validators: participants_bitfield = set_voted(participants_bitfield, committee.index(index)) previous_epoch_attestations.append( PendingAttestation( **sample_pending_attestation_record_params).copy( aggregation_bitfield=participants_bitfield, data=AttestationData( **sample_attestation_data_params).copy( target_epoch=previous_epoch, crosslink=Crosslink( shard=shard, parent_root=Crosslink().root, ), ), )) state = state.copy( previous_epoch_attestations=tuple(previous_epoch_attestations), ) rewards_received, penalties_received = get_crosslink_deltas( state, config, ) expected_rewards_received = { index: 0 for index in range(len(state.validators)) } validator_balance = max_effective_balance for i in range(slots_per_epoch): crosslink_committee, shard = prev_epoch_crosslink_committees[i] if shard not in valid_shards_for_epoch: continue attesting_validators = each_slot_attestion_validators_list[i] total_attesting_balance = len(attesting_validators) * validator_balance total_committee_balance = len(crosslink_committee) * validator_balance for index in crosslink_committee: if index in attesting_validators: reward = get_base_reward( state=state, index=index, config=config, ) * total_attesting_balance // total_committee_balance expected_rewards_received[index] += reward else: penalty = get_base_reward( state=state, index=index, config=config, ) expected_rewards_received[index] -= penalty # Check the rewards/penalties match for index in range(len(state.validators)): if index not in indices_to_check: continue assert (rewards_received[index] - penalties_received[index] == expected_rewards_received[index])
def test_get_attestation_deltas(genesis_state, config, slots_per_epoch, target_committee_size, shard_count, min_attestation_inclusion_delay, inactivity_penalty_quotient, finalized_epoch, current_slot, sample_pending_attestation_record_params, sample_attestation_data_params): state = genesis_state.copy( slot=current_slot, finalized_epoch=finalized_epoch, ) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) epoch_start_shard = get_epoch_start_shard( state, previous_epoch, CommitteeConfig(config), ) shard_delta = get_shard_delta( state, previous_epoch, CommitteeConfig(config), ) a = epoch_start_shard b = epoch_start_shard + shard_delta if a > b: valid_shards_for_epoch = range(b, a) else: valid_shards_for_epoch = range(a, b) indices_to_check = set() prev_epoch_start_slot = get_epoch_start_slot(previous_epoch, slots_per_epoch) prev_epoch_attestations = tuple() for slot in range(prev_epoch_start_slot, prev_epoch_start_slot + slots_per_epoch): committee, shard = get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), )[0] if not committee: continue if shard not in valid_shards_for_epoch: continue participants_bitfield = get_empty_bitfield(len(committee)) for i, index in enumerate(committee): indices_to_check.add(index) participants_bitfield = set_voted(participants_bitfield, i) prev_epoch_attestations += (PendingAttestation( **sample_pending_attestation_record_params).copy( aggregation_bitfield=participants_bitfield, inclusion_delay=min_attestation_inclusion_delay, proposer_index=get_beacon_proposer_index( state.copy(slot=slot, ), CommitteeConfig(config), ), data=AttestationData(**sample_attestation_data_params).copy( crosslink=Crosslink(shard=shard, ), target_epoch=previous_epoch, target_root=get_block_root( state, previous_epoch, config.SLOTS_PER_EPOCH, config.SLOTS_PER_HISTORICAL_ROOT, ), beacon_block_root=get_block_root_at_slot( state, slot, config.SLOTS_PER_HISTORICAL_ROOT, ), ), ), ) state = state.copy(previous_epoch_attestations=prev_epoch_attestations, ) rewards_received, penalties_received = get_attestation_deltas( state, config, ) # everyone attested, no penalties assert (sum(penalties_received) == 0) the_reward = rewards_received[0] # everyone performed the same, equal rewards assert (sum(rewards_received) // len(rewards_received) == the_reward)