diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index e47fcb45d..ced6bca27 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -260,15 +260,29 @@ func get_initial_beacon_state*( if get_effective_balance(state, vi) > MAX_DEPOSIT_AMOUNT: activate_validator(state, vi, true) - # set initial committee shuffling + # initial_shuffling + initial_shuffling in spec, but more ugly + # TODO remove temporary workaround + # previously, shuffling created foo[slot][committee_per_slot] + # now that's flattened to [committee_0_slot_0, c_1_s_0, ..., c_2_s_1, c_3_s_1, ...] + # so build adapter to keep this working until full conversion to current spec + # target structure is array[2 * EPOCH_LENGTH, seq[ShardCommittee]], + # where ShardCommittee is: shard*: uint64 / committee*: seq[Uint24] let initial_shuffling = - get_shuffling(Eth2Digest(), state.validator_registry, 0, state.slot) + get_shuffling(Eth2Digest(), state.validator_registry, state.slot) + committee_count_per_slot = initial_shuffling.len div EPOCH_LENGTH - # initial_shuffling + initial_shuffling in spec, but more ugly - for i, n in initial_shuffling: - state.shard_committees_at_slots[i] = n - state.shard_committees_at_slots[EPOCH_LENGTH + i] = n + for i in 0 ..< EPOCH_LENGTH: + state.shard_committees_at_slots[i] = @[] + state.shard_committees_at_slots[EPOCH_LENGTH + i] = @[] + + for i, committee2 in initial_shuffling: + let slot = i div committee_count_per_slot + var sc: ShardCommittee + sc.shard = i.uint64 + sc.committee = committee2 + state.shard_committees_at_slots[slot] = concat(state.shard_committees_at_slots[slot], @[sc]) + state.shard_committees_at_slots[EPOCH_LENGTH + slot] = concat(state.shard_committees_at_slots[EPOCH_LENGTH + slot], @[sc]) state diff --git a/beacon_chain/spec/validator.nim b/beacon_chain/spec/validator.nim index a60ae9c8e..813b9e7d8 100644 --- a/beacon_chain/spec/validator.nim +++ b/beacon_chain/spec/validator.nim @@ -22,43 +22,38 @@ func min_empty_validator_index*( ZERO_BALANCE_VALIDATOR_TTL.uint64 <= current_slot: return some(i) +func xorSeed(seed: Eth2Digest, x: uint64): Eth2Digest = + ## Integers are all encoded as bigendian + ## Helper for get_shuffling in lieu of generally better bitwise handling + ## xor least significant/highest-index 8 bytes in place (after copy) + result = seed + for i in 0 ..< 8: + result.data[31 - i] = result.data[31 - i] xor byte((x shr i*8) and 0xff) + func get_shuffling*(seed: Eth2Digest, validators: openArray[Validator], - crosslinking_start_shard: uint64, # TODO remove slot_nonaligned: uint64 - ): array[EPOCH_LENGTH, seq[ShardCommittee]] = - ## Split up validators into groups at the start of every epoch, - ## determining at what height they can make attestations and what shard they are making crosslinks for + ): seq[seq[Uint24]] = + ## Shuffles ``validators`` into crosslink committees seeded by ``seed`` and ``slot``. + ## Returns a list of ``EPOCH_LENGTH * committees_per_slot`` committees where each + ## committee is itself a list of validator indices. ## Implementation should do the following: http://vitalik.ca/files/ShuffleAndAssign.png let slot = slot_nonaligned - slot_nonaligned mod EPOCH_LENGTH + active_validator_indices = get_active_validator_indices(validators, slot) - committees_per_slot = clamp( - len(active_validator_indices) div EPOCH_LENGTH div TARGET_COMMITTEE_SIZE, - 1, SHARD_COUNT div EPOCH_LENGTH).uint64 - # Shuffle with seed - shuffled_active_validator_indices = shuffle(active_validator_indices, seed) - # Split the shuffled list into cycle_length pieces - validators_per_slot = split(shuffled_active_validator_indices, EPOCH_LENGTH) - assert validators_per_slot.len() == EPOCH_LENGTH # what split should do.. + committees_per_slot = get_committee_count_per_slot(len(active_validator_indices)).int - for slot, slot_indices in validators_per_slot: - let - shard_indices = split(slot_indices, committees_per_slot) - shard_id_start = - crosslinking_start_shard + slot.uint64 * committees_per_slot + # Shuffle + shuffled_active_validator_indices = shuffle( + active_validator_indices, + xorSeed(seed, slot)) - var committees = newSeq[ShardCommittee](shard_indices.len) - for shard_position, indices in shard_indices: - committees[shard_position].shard = - shard_id_start + shard_position.uint64 mod SHARD_COUNT.uint64 - committees[shard_position].committee = indices - committees[shard_position].total_validator_count = - len(active_validator_indices).uint64 - - result[slot] = committees + # Split the shuffled list into epoch_length * committees_per_slot pieces + result = split(shuffled_active_validator_indices, committees_per_slot * EPOCH_LENGTH) + assert result.len() == committees_per_slot * EPOCH_LENGTH # what split should do.. func get_new_validator_registry_delta_chain_tip*( current_validator_registry_delta_chain_tip: Eth2Digest, @@ -75,3 +70,58 @@ func get_new_validator_registry_delta_chain_tip*( slot: slot, flag: flag )) + +func get_previous_epoch_committee_count_per_slot(state: BeaconState): uint64 = + let previous_active_validators = get_active_validator_indices( + state.validator_registry, + state.previous_epoch_calculation_slot + ) + get_committee_count_per_slot(len(previous_active_validators)) + +func get_current_epoch_committee_count_per_slot(state: BeaconState): uint64 = + let previous_active_validators = get_active_validator_indices( + state.validator_registry, + state.current_epoch_calculation_slot + ) + get_committee_count_per_slot(len(previous_active_validators)) + +func get_crosslink_committees_at_slot*(state: BeaconState, slot: uint64) : seq[tuple[a: seq[Uint24], b: uint64]] = + ## Returns the list of ``(committee, shard)`` tuples for the ``slot``. + + let state_epoch_slot = state.slot - (state.slot mod EPOCH_LENGTH) + assert state_epoch_slot <= slot + EPOCH_LENGTH + assert slot < state_epoch_slot + EPOCH_LENGTH + let offset = slot mod EPOCH_LENGTH + + if slot < state_epoch_slot: + let + committees_per_slot = get_previous_epoch_committee_count_per_slot(state) + shuffling = get_shuffling( + state.previous_epoch_randao_mix, + state.validator_registry, + state.previous_epoch_calculation_slot + ) + slot_start_shard = (state.previous_epoch_start_shard + committees_per_slot * offset) mod SHARD_COUNT + + ## This duplication is ugly, but keeping for sake of closeness with spec code structure + ## There are better approaches in general. + for i in 0 ..< committees_per_slot.int: + result.add ( + shuffling[(committees_per_slot * offset + i.uint64).int], + (slot_start_shard + i.uint64) mod SHARD_COUNT + ) + else: + let + committees_per_slot = get_current_epoch_committee_count_per_slot(state) + shuffling = get_shuffling( + state.current_epoch_randao_mix, + state.validator_registry, + state.current_epoch_calculation_slot + ) + slot_start_shard = (state.current_epoch_start_shard + committees_per_slot * offset) mod SHARD_COUNT + + for i in 0 ..< committees_per_slot.int: + result.add ( + shuffling[(committees_per_slot * offset + i.uint64).int], + (slot_start_shard + i.uint64) mod SHARD_COUNT + ) diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index b1f1a15d0..a8787c727 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -611,13 +611,15 @@ func processEpoch(state: var BeaconState) = state.finalized_slot = state.justified_slot block: # Crosslinks - for sac in state.shard_committees_at_slots: - for shard_committee in sac: - if 3'u64 * total_attesting_balance(shard_committee) >= - 2'u64 * total_balance_sac(shard_committee): - state.latest_crosslinks[shard_committee.shard] = Crosslink( - slot: state.slot + EPOCH_LENGTH, - shard_block_root: winning_root(shard_committee)) + for slot in state.slot - 2 * EPOCH_LENGTH ..< state.slot: + discard + # TODO implement helpers + #let crosslink_committees_at_slot = get_crosslink_committees_at_slot(state, slot) + # + #for crosslink_committee, shard in crosslink_committees_at_slot.items: + # if 3 * total_attesting_balance(crosslink_committee) >= 2 * total_balance(crosslink_committee): + # state.latest_crosslinks[shard] = Crosslink( + # slot=state.slot, shard_block_root=winning_root(crosslink_committee)) block: # Justification and finalization let @@ -705,14 +707,6 @@ func processEpoch(state: var BeaconState) = state.shard_committees_at_slots[i] = state.shard_committees_at_slots[EPOCH_LENGTH + i] - let next_start_shard = - (state.shard_committees_at_slots[^1][^1].shard + 1) mod SHARD_COUNT - for i, v in get_shuffling( - state.latest_randao_mixes[ - (state.slot - EPOCH_LENGTH) mod LATEST_RANDAO_MIXES_LENGTH], - state.validator_registry, next_start_shard, state.slot): - state.shard_committees_at_slots[i + EPOCH_LENGTH] = v - else: # If a validator registry change does NOT happen for i in 0..