We probably care less about optimizing selection based on the following properties:
Select the inclusion list committee similarly to the beacon committee. Using the beacon state, epoch, and seed, we obtain the shuffled indices and divide them into slots. Subsets of these slot indices form the inclusion list committee.
def get_inclusion_list_committee(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, IL_COMMITTEE_SIZE]:
epoch = compute_epoch_at_slot(slot)
seed = get_seed(state, epoch, DOMAIN_IL_COMMITTEE)
indices = get_active_validator_indices(state, epoch)
start = (slot % SLOTS_PER_EPOCH) * IL_COMMITTEE_SIZE
end = start + IL_COMMITTEE_SIZE
return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)]
Remarks:
Here’s an improved version for readability and clarity:
Select the inclusion list committee similarly to how the proposer and sync committees are sampled. Shuffled indices are processed one by one, with effective balance influencing the selection outcome.
def get_inclusion_list_committee(state: BeaconState) -> Sequence[ValidatorIndex]:
epoch = Epoch(get_current_epoch(state) + 1)
MAX_RANDOM_BYTE = 2**8 - 1
active_validator_indices = get_active_validator_indices(state, epoch)
active_validator_count = uint64(len(active_validator_indices))
seed = get_seed(state, epoch, DOMAIN_IL_COMMITTEE)
i = 0
inclusion_list_committee_indices: List[ValidatorIndex] = []
while len(inclusion_list_committee_indices) < IL_COMMITTEE_SIZE:
shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed)
candidate_index = active_validator_indices[shuffled_index]
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
effective_balance = state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
inclusion_list_committee_indices.append(candidate_index)
i += 1
return inclusion_list_committee_indices
Remarks:
Select the inclusion list committee similarly to how the aggregator in the beacon committee is selected. First, a validator computes a slot signature using the inclusion list selection proof domain:
def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_IL_PROOF, compute_epoch_at_slot(slot))
signing_root = compute_signing_root(slot, domain)
return bls.Sign(privkey, signing_root)
Based on the slot signature, we determine whether the slot signature qualifies as part of the inclusion list committee. This reuses logic from the beacon committee:
def is_inclusion_list_member(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool:
committee = get_beacon_committee(state, slot, index)
modulo = max(1, len(committee) // TARGET_INCLUSION_LIST_PER_COMMITTEE)
return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0
Additionally, we add a selection proof to the inclusion list container and define another message to sign over it:
class InclusionListAndProof(Container):
validator_index: ValidatorIndex
message: InclusionList
selection_proof: BLSSignature
Remarks:
Final note, FOCIL currently implements (1) as it is the simplest approach and achieves most of the desired properties. However, if there is any critical property we are missing or a feature needed for future compatibility, we should identify it soon.