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 test_get_unslashed_attesting_indices(genesis_state, config): state = genesis_state.copy( slot=compute_start_slot_of_epoch(3, config.SLOTS_PER_EPOCH)) target_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) target_shard = (state.start_shard + 3) % config.SHARD_COUNT some_committee = get_crosslink_committee(state, target_epoch, target_shard, CommitteeConfig(config)) data = AttestationData(target=Checkpoint(epoch=target_epoch), crosslink=Crosslink(shard=target_shard)) some_subset_count = random.randrange(1, len(some_committee) // 2) some_subset = random.sample(some_committee, some_subset_count) bitfield = get_empty_bitfield(len(some_committee)) for i, index in enumerate(some_committee): if index in some_subset: if random.choice([True, False]): state = state.update_validator_with_fn( index, lambda v, *_: v.copy(slashed=True)) bitfield = set_voted(bitfield, i) some_subset = tuple( filter(lambda index: not state.validators[index].slashed, some_subset)) indices = get_unslashed_attesting_indices( state, (PendingAttestation(data=data, aggregation_bits=bitfield), ), CommitteeConfig(config), ) assert set(indices) == set(some_subset) assert len(indices) == len(some_subset)
def test_get_attesting_indices(genesis_state, config): state = genesis_state.copy( slot=get_epoch_start_slot(3, config.SLOTS_PER_EPOCH)) target_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) target_shard = (state.start_shard + 3) % config.SHARD_COUNT some_committee = get_crosslink_committee( state, target_epoch, target_shard, CommitteeConfig(config), ) data = AttestationData( target_epoch=target_epoch, crosslink=Crosslink(shard=target_shard, ), ) some_subset_count = random.randint(1, len(some_committee) // 2) some_subset = random.sample(some_committee, some_subset_count) bitfield = get_empty_bitfield(len(some_committee)) for i, index in enumerate(some_committee): if index in some_subset: bitfield = set_voted(bitfield, i) indices = get_attesting_indices( state, data, bitfield, CommitteeConfig(config), ) assert set(indices) == set(some_subset) assert len(indices) == len(some_subset)
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 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_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), )) 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_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 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=Checkpoint( epoch=epoch, ), crosslink=Crosslink( shard=shard, ), ) committee_count = len(committee) aggregation_bits = bitfield.get_empty_bitfield(committee_count) for index in range(committee_count): aggregation_bits = bitfield.set_voted(aggregation_bits, index) for index in committee: yield ( index, ( get_attestation_data_slot( state, attestation_data, config, ), ( aggregation_bits, attestation_data, ), ), )
def _validate_aggregation_bits(state: BeaconState, attestation: Attestation, config: CommitteeConfig) -> None: data = attestation.data committee = get_crosslink_committee(state, data.target.epoch, data.crosslink.shard, config) if not (len(attestation.aggregation_bits) == len(attestation.custody_bits) == len(committee)): raise ValidationError( f"The attestation bit lengths not match:" f"\tlen(attestation.aggregation_bits)={len(attestation.aggregation_bits)}\n" f"\tlen(attestation.custody_bits)={len(attestation.custody_bits)}" f"\tlen(committee)={len(committee)}")
def test_find_winning_crosslink_and_attesting_indices_from_candidates( genesis_state, number_of_candidates, config): some_epoch = config.GENESIS_EPOCH + 20 some_shard = 3 state = genesis_state.copy( slot=compute_start_slot_of_epoch(some_epoch, config.SLOTS_PER_EPOCH), start_shard=some_shard, current_crosslinks=tuple( Crosslink(shard=i, data_root=(i).to_bytes(32, "little")) for i in range(config.SHARD_COUNT)), ) full_committee = get_crosslink_committee(state, some_epoch, some_shard, CommitteeConfig(config)) # break the committees up into different subsets to simulate different # attestations for the same crosslink committees = tuple( random_sample( len(full_committee) // number_of_candidates, full_committee) for _ in range(number_of_candidates)) seen = set() filtered_committees = tuple() for committee in committees: deduplicated_committee = tuple() for index in committee: if index in seen: pass else: seen.add(index) deduplicated_committee += (index, ) filtered_committees += (deduplicated_committee, ) candidates = tuple( mk_pending_attestation_from_committee( state.current_crosslinks[some_shard], len(full_committee), some_shard, target_epoch=some_epoch, ) for committee in filtered_committees) if number_of_candidates == 0: expected_result = (Crosslink(), set()) else: expected_result = (candidates[0].data.crosslink, set(sorted(full_committee))) result = _find_winning_crosslink_and_attesting_indices_from_candidates( state, candidates, config) assert result == expected_result
def get_attesting_indices( state: BeaconState, attestation_data: AttestationData, bitfield: Bitfield, config: CommitteeConfig, ) -> Set[ValidatorIndex]: """ Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``. """ committee = get_crosslink_committee(state, attestation_data.target.epoch, attestation_data.crosslink.shard, config) return set(index for i, index in enumerate(committee) if has_voted(bitfield, i))
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_committee_count( len(active_validators), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) // config.SLOTS_PER_EPOCH ) epoch_start_slot = compute_start_slot_of_epoch(epoch, config.SLOTS_PER_EPOCH) epoch_start_shard = get_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 test_get_crosslink_committee(genesis_state, config): indices = tuple() for shard in range( get_shard_delta(genesis_state, config.GENESIS_EPOCH, CommitteeConfig(config))): some_committee = get_crosslink_committee( genesis_state, genesis_state.current_epoch(config.SLOTS_PER_EPOCH), genesis_state.start_shard + shard, CommitteeConfig(config), ) indices += tuple(some_committee) assert set(indices) == set(range(len(genesis_state.validators))) assert len(indices) == len(genesis_state.validators)
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_committee_count( len(active_validators), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) // config.SLOTS_PER_EPOCH epoch_start_slot = compute_start_slot_of_epoch( epoch, config.SLOTS_PER_EPOCH, ) epoch_start_shard = get_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=Checkpoint( epoch=epoch, ), crosslink=Crosslink( shard=shard, ), ) committee_count = len(committee) aggregation_bits = bitfield.get_empty_bitfield(committee_count) for i in range(committee_count): aggregation_bits = bitfield.set_voted(aggregation_bits, i) return { index: ( slot, (aggregation_bits, 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 test_find_candidate_attestations_for_shard(genesis_state, config): some_epoch = config.GENESIS_EPOCH + 20 # start on some shard and walk a subset of them some_shard = 3 shard_offset = 24 state = genesis_state.copy( slot=compute_start_slot_of_epoch(some_epoch, config.SLOTS_PER_EPOCH), start_shard=some_shard, current_crosslinks=tuple( Crosslink(shard=i, data_root=(i).to_bytes(32, "little")) for i in range(config.SHARD_COUNT)), ) # sample a subset of the shards to make attestations for some_shards_with_attestations = random.sample( range(some_shard, some_shard + shard_offset), shard_offset // 2) committee_and_shard_pairs = tuple( ( get_crosslink_committee(state, some_epoch, some_shard + i, CommitteeConfig(config)), some_shard + i, ) for i in range(shard_offset) if some_shard + i in some_shards_with_attestations) pending_attestations = { shard: mk_pending_attestation_from_committee(state.current_crosslinks[shard], len(committee), shard) for committee, shard in committee_and_shard_pairs } # invalidate some crosslinks to test the crosslink filter some_crosslinks_to_mangle = random.sample( some_shards_with_attestations, len(some_shards_with_attestations) // 2) shards_with_valid_crosslinks = set(some_shards_with_attestations) - set( some_crosslinks_to_mangle) crosslinks = tuple() for shard in range(config.SHARD_COUNT): if shard in shards_with_valid_crosslinks: crosslinks += (state.current_crosslinks[shard], ) else: crosslinks += (Crosslink(), ) state = state.copy(current_crosslinks=crosslinks) # check around the range of shards we built up for shard in range(0, some_shard + shard_offset + 3): if shard in some_shards_with_attestations: attestations = _get_attestations_for_shard( pending_attestations.values(), shard) assert attestations == (pending_attestations[shard], ) if shard in some_crosslinks_to_mangle: assert not _get_attestations_for_valid_crosslink( pending_attestations.values(), state, shard, config) else: attestations = _get_attestations_for_valid_crosslink( pending_attestations.values(), state, shard, config) assert attestations == (pending_attestations[shard], ) else: assert not _get_attestations_for_shard( pending_attestations.values(), shard) assert not _get_attestations_for_valid_crosslink( pending_attestations.values(), state, shard, config)