diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 545610ab4..ff90bc561 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -89,13 +89,13 @@ proc sync*(node: BeaconNode): Future[bool] {.async.} = node.beaconState = await obtainTrustedStateSnapshot(node.db) else: node.beaconState = persistedState[] - var targetSlot = toSlot timeSinceGenesis(node.beaconState) + var targetSlot = (toSlot timeSinceGenesis(node.beaconState)) let t = now() if t < node.beaconState.genesisTime * 1000: await sleepAsync int(node.beaconState.genesisTime * 1000 - t) - while node.beaconState.finalized_slot < targetSlot: + while node.beaconState.finalized_epoch < targetSlot.slot_to_epoch: var (peer, changeLog) = await node.network.getValidatorChangeLog( node.beaconState.validator_registry_delta_chain_tip) @@ -152,10 +152,10 @@ proc makeAttestation(node: BeaconNode, doAssert node != nil doAssert validator != nil - if node.beaconState.slot == node.beaconState.justified_slot: + if get_current_epoch(node.beaconState) == node.beaconState.justified_epoch: return - let justifiedBlockRoot = get_block_root(node.beaconState, node.beaconState.justified_slot) + let justifiedBlockRoot = get_block_root(node.beaconState, get_epoch_start_slot(node.beaconState.justified_epoch)) var attestationData = AttestationData( slot: slot, @@ -164,7 +164,7 @@ proc makeAttestation(node: BeaconNode, epoch_boundary_root: Eth2Digest(), # TODO shard_block_root: Eth2Digest(), # TODO latest_crosslink_root: Eth2Digest(), # TODO - justified_slot: node.beaconState.justified_slot, + justified_epoch: node.beaconState.justified_epoch, justified_block_root: justifiedBlockRoot) let validatorSignature = await validator.signAttestation(attestationData) diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 122829c5b..51cae3fff 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -39,7 +39,7 @@ func validate_proof_of_possession(state: BeaconState, hash_tree_root_final(proof_of_possession_data).data, proof_of_possession, get_domain( - state.fork_data, + state.fork, state.slot, DOMAIN_DEPOSIT, ) @@ -70,10 +70,10 @@ func process_deposit(state: var BeaconState, withdrawal_credentials: withdrawal_credentials, randao_commitment: randao_commitment, randao_layers: 0, - activation_slot: FAR_FUTURE_SLOT, - exit_slot: FAR_FUTURE_SLOT, - withdrawal_slot: FAR_FUTURE_SLOT, - penalized_slot: FAR_FUTURE_SLOT, + activation_epoch: FAR_FUTURE_EPOCH, + exit_epoch: FAR_FUTURE_EPOCH, + withdrawal_epoch: FAR_FUTURE_EPOCH, + penalized_epoch: FAR_FUTURE_EPOCH, exit_count: 0, status_flags: 0, custody_commitment: custody_commitment, @@ -101,21 +101,19 @@ func process_deposit(state: var BeaconState, state.validator_balances[index] += deposit index.Uint24 +func get_entry_exit_effect_epoch*(epoch: EpochNumber): EpochNumber = + ## An entry or exit triggered in the ``epoch`` given by the input takes effect at + ## the epoch given by the output. + epoch + 1 + ENTRY_EXIT_DELAY + +# TODO: Uint24 -> ValidatorIndex func activate_validator(state: var BeaconState, index: Uint24, genesis: bool) = ## Activate the validator with the given ``index``. let validator = addr state.validator_registry[index] - validator.activation_slot = if genesis: GENESIS_SLOT else: state.slot + ENTRY_EXIT_DELAY - state.validator_registry_delta_chain_tip = - get_new_validator_registry_delta_chain_tip( - state.validator_registry_delta_chain_tip, - index, - validator.pubkey, - validator.activation_slot, - ACTIVATION, - ) + validator.activation_epoch = if genesis: GENESIS_EPOCH else: get_entry_exit_effect_epoch(get_current_epoch(state)) func initiate_validator_exit(state: var BeaconState, index: Uint24) = @@ -131,44 +129,29 @@ func exit_validator*(state: var BeaconState, let validator = addr state.validator_registry[index] # The following updates only occur if not previous exited - if validator.exit_slot <= state.slot + ENTRY_EXIT_DELAY: + if validator.exit_epoch <= get_entry_exit_effect_epoch(get_current_epoch(state)): return - validator.exit_slot = state.slot + ENTRY_EXIT_DELAY + validator.exit_epoch = get_entry_exit_effect_epoch(get_current_epoch(state)) # The following updates only occur if not previous exited state.validator_registry_exit_count += 1 validator.exit_count = state.validator_registry_exit_count - state.validator_registry_delta_chain_tip = - get_new_validator_registry_delta_chain_tip( - state.validator_registry_delta_chain_tip, - index, - validator.pubkey, - validator.exit_slot, - ValidatorSetDeltaFlags.EXIT - ) - -func process_penalties_and_exits_eligible(state: BeaconState, index: int): bool = - let validator = state.validator_registry[index] - if validator.penalized_slot <= state.slot: - # strangely uppercase variable-ish name - let PENALIZED_WITHDRAWAL_TIME = (LATEST_PENALIZED_EXIT_LENGTH * EPOCH_LENGTH div 2).uint64 - return state.slot >= validator.penalized_slot + PENALIZED_WITHDRAWAL_TIME - else: - return state.slot >= validator.exit_slot + MIN_VALIDATOR_WITHDRAWAL_TIME func process_penalties_and_exits(state: var BeaconState) = - # The active validators - let active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) + let + current_epoch = get_current_epoch(state) + # The active validators + active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) # The total effective balance of active validators var total_balance : uint64 = 0 for i in active_validator_indices: total_balance += get_effective_balance(state, i) for index, validator in state.validator_registry: - if (state.slot div EPOCH_LENGTH) == (validator.penalized_slot div EPOCH_LENGTH) + LATEST_PENALIZED_EXIT_LENGTH div 2: + if current_epoch == validator.penalized_epoch + LATEST_PENALIZED_EXIT_LENGTH div 2: let - e = ((state.slot div EPOCH_LENGTH) mod LATEST_PENALIZED_EXIT_LENGTH).int + e = (current_epoch mod LATEST_PENALIZED_EXIT_LENGTH).int total_at_start = state.latest_penalized_exit_balances[(e + 1) mod LATEST_PENALIZED_EXIT_LENGTH] total_at_end = state.latest_penalized_exit_balances[e] total_penalties = total_at_end - total_at_start @@ -211,29 +194,31 @@ func get_initial_beacon_state*( # Misc slot: GENESIS_SLOT, genesis_time: genesis_time, - fork_data: ForkData( + # TODO pre_fork_version -> previous_version, post_fork_version -> current_version, + # rm fork_slot init in favor of epoch + fork: Fork( pre_fork_version: GENESIS_FORK_VERSION, post_fork_version: GENESIS_FORK_VERSION, fork_slot: GENESIS_SLOT, ), - validator_registry_update_slot: GENESIS_SLOT, + validator_registry_update_epoch: GENESIS_EPOCH, validator_registry_exit_count: 0, validator_registry_delta_chain_tip: ZERO_HASH, # Randomness and committees previous_epoch_start_shard: GENESIS_START_SHARD, current_epoch_start_shard: GENESIS_START_SHARD, - previous_epoch_calculation_slot: GENESIS_SLOT, - current_epoch_calculation_slot: GENESIS_SLOT, - previous_epoch_randao_mix: ZERO_HASH, - current_epoch_randao_mix: ZERO_HASH, + previous_calculation_epoch: GENESIS_EPOCH, + current_calculation_epoch: GENESIS_EPOCH, + previous_epoch_seed: ZERO_HASH, + current_epoch_seed: ZERO_HASH, # Finality - previous_justified_slot: GENESIS_SLOT, - justified_slot: GENESIS_SLOT, + previous_justified_epoch: GENESIS_EPOCH, + justified_epoch: GENESIS_EPOCH, justification_bitfield: 0, - finalized_slot: GENESIS_SLOT, + finalized_epoch: GENESIS_EPOCH, # Deposit root latest_eth1_data: latest_eth1_data, @@ -268,13 +253,6 @@ func get_block_root*(state: BeaconState, doAssert slot < state.slot state.latest_block_roots[slot mod LATEST_BLOCK_ROOTS_LENGTH] -func get_randao_mix*(state: BeaconState, - slot: uint64): Eth2Digest = - ## Returns the randao mix at a recent ``slot``. - assert state.slot < slot + LATEST_RANDAO_MIXES_LENGTH - assert slot <= state.slot - state.latest_randao_mixes[slot mod LATEST_RANDAO_MIXES_LENGTH] - func get_attestation_participants*(state: BeaconState, attestation_data: AttestationData, aggregation_bitfield: seq[byte]): seq[Uint24] = @@ -315,6 +293,8 @@ func process_ejections*(state: var BeaconState) = func update_validator_registry*(state: var BeaconState) = let + current_epoch = get_current_epoch(state) + next_epoch = current_epoch + 1 active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) # The total effective balance of active validators @@ -329,7 +309,7 @@ func update_validator_registry*(state: var BeaconState) = # Activate validators within the allowable balance churn var balance_churn = 0'u64 for index, validator in state.validator_registry: - if validator.activation_slot > state.slot + ENTRY_EXIT_DELAY and + if validator.activation_epoch > get_entry_exit_effect_epoch(current_epoch) and state.validator_balances[index] >= MAX_DEPOSIT_AMOUNT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index.Uint24) @@ -342,7 +322,7 @@ func update_validator_registry*(state: var BeaconState) = # Exit validators within the allowable balance churn balance_churn = 0 for index, validator in state.validator_registry: - if (validator.exit_slot > state.slot + ENTRY_EXIT_DELAY) and + if validator.exit_epoch > get_entry_exit_effect_epoch(current_epoch) and ((validator.status_flags and INITIATED_EXIT) == INITIATED_EXIT): # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index.Uint24) @@ -352,18 +332,20 @@ func update_validator_registry*(state: var BeaconState) = # Exit validator exit_validator(state, index.Uint24) + state.validator_registry_update_epoch = current_epoch + # Perform additional updates - state.previous_epoch_calculation_slot = state.current_epoch_calculation_slot - state.previous_epoch_start_shard = state.current_epoch_start_shard - state.previous_epoch_randao_mix = state.current_epoch_randao_mix - state.current_epoch_calculation_slot = state.slot - state.current_epoch_start_shard = (state.current_epoch_start_shard + get_current_epoch_committee_count_per_slot(state) * EPOCH_LENGTH) mod SHARD_COUNT - state.current_epoch_randao_mix = get_randao_mix(state, state.current_epoch_calculation_slot - SEED_LOOKAHEAD) + state.current_calculation_epoch = next_epoch + state.current_epoch_start_shard = (state.current_epoch_start_shard + get_current_epoch_committee_count(state)) mod SHARD_COUNT + state.current_epoch_seed = generate_seed(state, state.current_calculation_epoch) # TODO "If a validator registry update does not happen do the following: ..." process_penalties_and_exits(state) +func get_epoch_start_slot*(epoch: EpochNumber): SlotNumber = + epoch * EPOCH_LENGTH + proc checkAttestation*( state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool = ## Check that an attestation follows the rules of being included in the state @@ -382,20 +364,20 @@ proc checkAttestation*( attestation_slot = attestation.data.slot, state_slot = state.slot) return - let expected_justified_slot = - if attestation.data.slot >= state.slot - (state.slot mod EPOCH_LENGTH): - state.justified_slot + let expected_justified_epoch = + if attestation.data.slot >= get_epoch_start_slot(get_current_epoch(state)): + state.justified_epoch else: - state.previous_justified_slot + state.previous_justified_epoch - if not (attestation.data.justified_slot == expected_justified_slot): - warn("Unexpected justified slot", - attestation_justified_slot = attestation.data.justified_slot, - expected_justified_slot) + if not (attestation.data.justified_epoch == expected_justified_epoch): + warn("Unexpected justified epoch", + attestation_justified_epoch = attestation.data.justified_epoch, + expected_justified_epoch) return let expected_justified_block_root = - get_block_root(state, attestation.data.justified_slot) + get_block_root(state, get_epoch_start_slot(attestation.data.justified_epoch)) if not (attestation.data.justified_block_root == expected_justified_block_root): warn("Unexpected justified block root", attestation_justified_block_root = attestation.data.justified_block_root, @@ -420,7 +402,7 @@ proc checkAttestation*( if not bls_verify( group_public_key, @(msg.data) & @[0'u8], attestation.aggregate_signature, - get_domain(state.fork_data, attestation.data.slot, DOMAIN_ATTESTATION) + get_domain(state.fork, attestation.data.slot, DOMAIN_ATTESTATION) ): warn("Invalid attestation group signature") return diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index 618273ae7..2050fb6ed 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -75,9 +75,6 @@ const BEACON_CHAIN_SHARD_NUMBER* = not 0'u64 # 2^64 - 1 in spec MAX_CASPER_VOTES* = 2^10 - LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13 - LATEST_RANDAO_MIXES_LENGTH* = 2'u64^13 - LATEST_PENALIZED_EXIT_LENGTH* = 8192 # epochs MAX_WITHDRAWALS_PER_EPOCH* = 4 # withdrawals # Deposit contract @@ -89,12 +86,19 @@ const MAX_DEPOSIT_AMOUNT* = 2'u64^5 * GWEI_PER_ETH ##\ ## Maximum amounth of ETH that can be deposited in one call + # Time parameter, here so that GENESIS_EPOCH can access it + EPOCH_LENGTH* = 64 ##\ + ## (~6.4 minutes) + ## slots that make up an epoch, at the end of which more heavy + ## processing is done + # Initial values GENESIS_FORK_VERSION* = 0'u64 - GENESIS_SLOT* = 0'u64 + GENESIS_SLOT* = 2'u64^19 + GENESIS_EPOCH* = GENESIS_SLOT div EPOCH_LENGTH # slot_to_epoch(GENESIS_SLOT) GENESIS_START_SHARD* = 0'u64 - FAR_FUTURE_SLOT* = not 0'u64 # 2^64 - 1 in spec + FAR_FUTURE_EPOCH* = not 0'u64 # 2^64 - 1 in spec ZERO_HASH* = Eth2Digest() # TODO EMPTY_SIGNATURE* = BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8 @@ -115,11 +119,6 @@ const ## wait towards the end of the slot and still have time to publish the ## attestation. - EPOCH_LENGTH* = 64 ##\ - ## (~6.4 minutes) - ## slots that make up an epoch, at the end of which more heavy - ## processing is done - ETH1_DATA_VOTING_PERIOD* = 2'u64^10 ##\ ## slots (~1.7 hours) @@ -138,6 +137,12 @@ const MIN_VALIDATOR_WITHDRAWAL_TIME* = 2'u64^14 ##\ ## slots (~27 hours) + # State list lengths + LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13 + LATEST_RANDAO_MIXES_LENGTH* = 2'u64^13 + LATEST_INDEX_ROOTS_LENGTH* = 2'u64^13 + LATEST_PENALIZED_EXIT_LENGTH* = 8192 # epochs + # Quotients BASE_REWARD_QUOTIENT* = 2'u64^10 ##\ ## The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It @@ -161,6 +166,8 @@ const type Uint24* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around + SlotNumber* = uint64 + EpochNumber* = uint64 # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#data-structures ProposerSlashing* = object @@ -210,8 +217,8 @@ type latest_crosslink_root*: Eth2Digest ##\ ## Last crosslink hash - justified_slot*: uint64 ##\ - ## Slot of last justified beacon block + justified_epoch*: uint64 ##\ + ## Epoch of last justified beacon block justified_block_root*: Eth2Digest ##\ ## Hash of last justified beacon block @@ -279,17 +286,9 @@ type proposer_slashings*: seq[ProposerSlashing] casper_slashings*: seq[CasperSlashing] attestations*: seq[Attestation] - custody_reseeds*: seq[CustodyReseed] - custody_challenges*: seq[CustodyChallenge] - custody_responses*: seq[CustodyResponse] deposits*: seq[Deposit] exits*: seq[Exit] - # Phase1: - CustodyReseed* = object - CustodyChallenge* = object - CustodyResponse* = object - ProposalSignedData* = object slot*: uint64 shard*: uint64 ##\ @@ -299,7 +298,7 @@ type BeaconState* = object slot*: uint64 genesis_time*: uint64 - fork_data*: ForkData ##\ + fork*: Fork ##\ ## For versioning hard forks # Validator registry @@ -307,36 +306,33 @@ type validator_balances*: seq[uint64] ##\ ## Validator balances in Gwei! - validator_registry_update_slot*: uint64 + validator_registry_update_epoch*: uint64 validator_registry_exit_count*: uint64 + + # TODO remove, not in spec anymore validator_registry_delta_chain_tip*: Eth2Digest ##\ ## For light clients to easily track delta # Randomness and committees latest_randao_mixes*: array[LATEST_BLOCK_ROOTS_LENGTH.int, Eth2Digest] - latest_vdf_outputs*: array[ - (LATEST_RANDAO_MIXES_LENGTH div EPOCH_LENGTH).int, Eth2Digest] - previous_epoch_start_shard*: uint64 current_epoch_start_shard*: uint64 - previous_epoch_calculation_slot*: uint64 - current_epoch_calculation_slot*: uint64 - previous_epoch_randao_mix*: Eth2Digest - current_epoch_randao_mix*: Eth2Digest - - # Custody challenges - custody_challenges*: seq[CustodyChallenge] + previous_calculation_epoch*: EpochNumber + current_calculation_epoch*: EpochNumber + previous_epoch_seed*: Eth2Digest + current_epoch_seed*: Eth2Digest # Finality - previous_justified_slot*: uint64 - justified_slot*: uint64 + previous_justified_epoch*: EpochNumber + justified_epoch*: EpochNumber justification_bitfield*: uint64 - finalized_slot*: uint64 + finalized_epoch*: EpochNumber # Recent state latest_crosslinks*: array[SHARD_COUNT, Crosslink] latest_block_roots*: array[LATEST_BLOCK_ROOTS_LENGTH.int, Eth2Digest] ##\ ## Needed to process attestations, older to newer + latest_index_roots*: array[LATEST_INDEX_ROOTS_LENGTH.int, Eth2Digest] latest_penalized_exit_balances*: seq[uint64] ##\ ## Balances penalized in the current withdrawal period @@ -350,6 +346,8 @@ type Validator* = object pubkey*: ValidatorPubKey withdrawal_credentials*: Eth2Digest + + # TODO remove randao_commitment, randao_layers, latest_status_change_slot, custody_commitment, latest_custody_reseed_slot, penultimate_custody_reseed_slot randao_commitment*: Eth2Digest ##\ ## RANDAO commitment created by repeatedly taking the hash of a secret value ## so as to create "onion layers" around it. For every block that a @@ -365,16 +363,16 @@ type latest_status_change_slot*: uint64 ##\ ## Slot when validator last changed status (or 0) - activation_slot*: uint64 ##\ + activation_epoch*: EpochNumber ##\ ## Slot when validator activated - exit_slot*: uint64 ##\ + exit_epoch*: EpochNumber ##\ ## Slot when validator exited - withdrawal_slot*: uint64 ##\ + withdrawal_epoch*: EpochNumber ##\ ## Slot when validator withdrew - penalized_slot*: uint64 ##\ + penalized_epoch*: EpochNumber ##\ ## Slot when validator penalized exit_count*: uint64 ##\ @@ -391,7 +389,7 @@ type ## Slot of second-latest custody reseed Crosslink* = object - slot*: uint64 + epoch*: uint64 shard_block_root*: Eth2Digest ##\ ## Shard chain block root @@ -421,7 +419,7 @@ type custody_bitfield*: seq[byte] # Proof of custody bitfield slot_included*: uint64 # Slot in which it was included - ForkData* = object + Fork* = object pre_fork_version*: uint64 # Previous fork version post_fork_version*: uint64 # Post fork version fork_slot*: uint64 # Fork slot number diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 7c0d774c6..32e7b7175 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -119,14 +119,14 @@ func integer_squareroot*(n: SomeInteger): SomeInteger = y = (x + n div x) div 2 x -func get_fork_version*(fork_data: ForkData, slot: uint64): uint64 = - if slot < fork_data.fork_slot: fork_data.pre_fork_version - else: fork_data.post_fork_version +func get_fork_version*(fork: Fork, slot: uint64): uint64 = + if slot < fork.fork_slot: fork.pre_fork_version + else: fork.post_fork_version func get_domain*( - fork_data: ForkData, slot: uint64, domain_type: SignatureDomain): uint64 = + fork: Fork, slot: uint64, domain_type: SignatureDomain): uint64 = # TODO Slot overflow? Or is slot 32 bits for all intents and purposes? - (get_fork_version(fork_data, slot) shl 32) + domain_type.uint32 + (get_fork_version(fork, slot) shl 32) + domain_type.uint32 func is_power_of_2*(v: uint64): bool = (v and (v-1)) == 0 @@ -147,6 +147,9 @@ proc is_double_vote*(attestation_data_1: AttestationData, ## same slot - doing so means risking getting slashed. attestation_data_1.slot == attestation_data_2.slot +func slot_to_epoch*(slot: SlotNumber): EpochNumber = + slot div EPOCH_LENGTH + proc is_surround_vote*(attestation_data_1: AttestationData, attestation_data_2: AttestationData): bool = ## Assumes ``attestation_data_1`` is distinct from ``attestation_data_2``. @@ -154,30 +157,65 @@ proc is_surround_vote*(attestation_data_1: AttestationData, ## due to a 'surround vote'. ## Note: parameter order matters as this function only checks ## that ``attestation_data_1`` surrounds ``attestation_data_2``. + let + source_epoch_1 = attestation_data_1.justified_epoch + source_epoch_2 = attestation_data_2.justified_epoch + target_epoch_1 = slot_to_epoch(attestation_data_1.slot) + target_epoch_2 = slot_to_epoch(attestation_data_2.slot) + ( - (attestation_data_1.justified_slot < attestation_data_2.justified_slot) and - (attestation_data_1.justified_slot + 1 == attestation_data_2.slot) and - (attestation_data_2.slot < attestation_data_1.slot) + (source_epoch_1 < source_epoch_2) and + (source_epoch_2 + 1 == target_epoch_2) and + (target_epoch_2 < target_epoch_1) ) -func is_active_validator*(validator: Validator, slot: uint64): bool = +func is_active_validator*(validator: Validator, epoch: EpochNumber): bool = ### Checks if validator is active - validator.activation_slot <= slot and slot < validator.exit_slot + validator.activation_epoch <= epoch and epoch < validator.exit_epoch -func get_active_validator_indices*(validators: openArray[Validator], slot: uint64): seq[Uint24] = +# TODO Uint24 -> ValidatorIndex +func get_active_validator_indices*(validators: openArray[Validator], epoch: EpochNumber): seq[Uint24] = ## Gets indices of active validators from validators for idx, val in validators: - if is_active_validator(val, slot): + if is_active_validator(val, epoch): result.add idx.Uint24 -func get_committee_count_per_slot*(active_validator_count: int): uint64 = +func get_epoch_committee_count*(active_validator_count: int): uint64 = clamp( active_validator_count div EPOCH_LENGTH div TARGET_COMMITTEE_SIZE, - 1, SHARD_COUNT div EPOCH_LENGTH).uint64 + 1, SHARD_COUNT div EPOCH_LENGTH).uint64 * EPOCH_LENGTH -func get_current_epoch_committee_count_per_slot*(state: BeaconState): uint64 = +func get_current_epoch_committee_count*(state: BeaconState): uint64 = let current_active_validators = get_active_validator_indices( state.validator_registry, - state.current_epoch_calculation_slot, + state.current_calculation_epoch, ) - return get_committee_count_per_slot(len(current_active_validators)) + return get_epoch_committee_count(len(current_active_validators)) + +func get_current_epoch*(state: BeaconState): EpochNumber = + slot_to_epoch(state.slot) + +## TODO I've been moving things into helpers because of layering issues +## but create DAG of which helper functions ref others and topo sort to +## refactor all of this mess. +func get_randao_mix*(state: BeaconState, + epoch: EpochNumber): Eth2Digest = + ## Returns the randao mix at a recent ``epoch``. + assert get_current_epoch(state) - LATEST_RANDAO_MIXES_LENGTH < epoch + assert epoch <= get_current_epoch(state) + + state.latest_randao_mixes[epoch mod LATEST_RANDAO_MIXES_LENGTH] + +func get_active_index_root(state: BeaconState, epoch: EpochNumber): Eth2Digest = + # Returns the index root at a recent ``epoch``. + assert get_current_epoch(state) - LATEST_INDEX_ROOTS_LENGTH < epoch + assert epoch <= get_current_epoch(state) + state.latest_index_roots[epoch mod LATEST_INDEX_ROOTS_LENGTH] + +func generate_seed*(state: BeaconState, epoch: EpochNumber): Eth2Digest = + # Generate a seed for the given ``epoch``. + + var seed_input : array[32*2, byte] + seed_input[0..31] = get_randao_mix(state, epoch - SEED_LOOKAHEAD).data + seed_input[32..63] = get_active_index_root(state, epoch).data + eth2hash(seed_input) diff --git a/beacon_chain/spec/validator.nim b/beacon_chain/spec/validator.nim index d18446cca..5c04910c9 100644 --- a/beacon_chain/spec/validator.nim +++ b/beacon_chain/spec/validator.nim @@ -30,9 +30,10 @@ func xorSeed(seed: Eth2Digest, x: uint64): Eth2Digest = for i in 0 ..< 8: result.data[31 - i] = result.data[31 - i] xor byte((x shr i*8) and 0xff) +# TODO Uint24 -> ValidatorIndex func get_shuffling*(seed: Eth2Digest, validators: openArray[Validator], - slot_nonaligned: uint64 + epoch: EpochNumber ): 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 @@ -40,20 +41,18 @@ func get_shuffling*(seed: Eth2Digest, ## 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, epoch) - active_validator_indices = get_active_validator_indices(validators, slot) - - committees_per_slot = get_committee_count_per_slot(len(active_validator_indices)).int + committees_per_epoch = get_epoch_committee_count(len(active_validator_indices)).int # Shuffle shuffled_active_validator_indices = shuffle( active_validator_indices, - xorSeed(seed, slot)) + xorSeed(seed, epoch)) - # 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.. + # Split the shuffled list into committees_per_epoch pieces + result = split(shuffled_active_validator_indices, committees_per_epoch) + assert result.len() == committees_per_epoch # what split should do.. func get_new_validator_registry_delta_chain_tip*( current_validator_registry_delta_chain_tip: Eth2Digest, @@ -71,60 +70,65 @@ func get_new_validator_registry_delta_chain_tip*( flag: flag )) -func get_previous_epoch_committee_count_per_slot(state: BeaconState): uint64 = +func get_previous_epoch_committee_count(state: BeaconState): uint64 = let previous_active_validators = get_active_validator_indices( state.validator_registry, - state.previous_epoch_calculation_slot + state.previous_calculation_epoch, ) - get_committee_count_per_slot(len(previous_active_validators)) + get_epoch_committee_count(len(previous_active_validators)) func get_current_epoch_committee_count_per_slot(state: BeaconState): uint64 = - let previous_active_validators = get_active_validator_indices( + let current_active_validators = get_active_validator_indices( state.validator_registry, - state.current_epoch_calculation_slot + state.current_calculation_epoch, ) - get_committee_count_per_slot(len(previous_active_validators)) + get_epoch_committee_count(len(current_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 + let + epoch = slot_to_epoch(slot) + current_epoch = get_current_epoch(state) + previous_epoch = if current_epoch > GENESIS_EPOCH: (current_epoch - 1) else: current_epoch + next_epoch = current_epoch + 1 - 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 + assert previous_epoch <= epoch + assert epoch < next_epoch - ## 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 + func get_epoch_specific_params() : auto = + if epoch < current_epoch: + let + committees_per_epoch = get_previous_epoch_committee_count(state) + seed = state.previous_epoch_seed + shuffling_epoch = state.previous_calculation_epoch + shuffling_start_shard = state.previous_epoch_start_shard + (committees_per_epoch, seed, shuffling_epoch, shuffling_start_shard) + else: + let + committees_per_epoch = get_current_epoch_committee_count(state) + seed = state.current_epoch_seed + shuffling_epoch = state.current_calculation_epoch + shuffling_start_shard = state.current_epoch_start_shard + (committees_per_epoch, seed, shuffling_epoch, shuffling_start_shard) - 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 - ) + let (committees_per_epoch, seed, shuffling_epoch, shuffling_start_shard) = get_epoch_specific_params() + + let + shuffling = get_shuffling( + seed, + state.validator_registry, + shuffling_epoch, + ) + offset = slot mod EPOCH_LENGTH + committees_per_slot = committees_per_epoch div EPOCH_LENGTH + slot_start_shard = (shuffling_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 + ) func get_shard_committees_at_slot*( state: BeaconState, slot: uint64): seq[ShardCommittee] = diff --git a/beacon_chain/state_transition.nim b/beacon_chain/state_transition.nim index 50ede79a7..f5ddb2563 100644 --- a/beacon_chain/state_transition.nim +++ b/beacon_chain/state_transition.nim @@ -62,7 +62,7 @@ func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool = bls_verify( state.validator_registry[proposer_index].pubkey, proposal_hash.data, blck.signature, - get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL)) + get_domain(state.fork, state.slot, DOMAIN_PROPOSAL)) proc processRandao( state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = @@ -127,7 +127,7 @@ func penalizeValidator(state: var BeaconState, index: Uint24) = whistleblower_reward = get_effective_balance(state, index) div WHISTLEBLOWER_REWARD_QUOTIENT state.validator_balances[whistleblower_index] += whistleblower_reward state.validator_balances[index] -= whistleblower_reward - validator.penalized_slot = state.slot + validator.penalized_epoch = get_current_epoch(state) proc processProposerSlashings( state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool = @@ -146,7 +146,7 @@ proc processProposerSlashings( hash_tree_root_final(proposer_slashing.proposal_data_1).data, proposer_slashing.proposal_signature_1, get_domain( - state.fork_data, proposer_slashing.proposal_data_1.slot, + state.fork, proposer_slashing.proposal_data_1.slot, DOMAIN_PROPOSAL)): notice "PropSlash: invalid signature 1" return false @@ -155,7 +155,7 @@ proc processProposerSlashings( hash_tree_root_final(proposer_slashing.proposal_data_2).data, proposer_slashing.proposal_signature_2, get_domain( - state.fork_data, proposer_slashing.proposal_data_2.slot, + state.fork, proposer_slashing.proposal_data_2.slot, DOMAIN_PROPOSAL)): notice "PropSlash: invalid signature 2" return false @@ -175,7 +175,7 @@ proc processProposerSlashings( notice "PropSlash: block root mismatch" return false - if not (proposer.penalized_slot > state.slot): + if not (proposer.penalized_epoch > get_current_epoch(state)): notice "PropSlash: penalized slot" return false @@ -240,7 +240,7 @@ proc processCasperSlashings(state: var BeaconState, blck: BeaconBlock): bool = return false for i in intersection: - if state.validator_registry[i].penalized_slot > state.slot: + if state.validator_registry[i].penalized_epoch > get_current_epoch(state): penalize_validator(state, i) return true @@ -296,11 +296,11 @@ proc processExits( if skipValidation notin flags: if not bls_verify( validator.pubkey, ZERO_HASH.data, exit.signature, - get_domain(state.fork_data, exit.slot, DOMAIN_EXIT)): + get_domain(state.fork, exit.slot, DOMAIN_EXIT)): notice "Exit: invalid signature" return false - if not (validator.exit_slot > state.slot + ENTRY_EXIT_DELAY): + if not (validator.exit_epoch > get_entry_exit_effect_epoch(get_current_epoch(state))): notice "Exit: exit/entry too close" return false @@ -419,7 +419,7 @@ func boundary_attestations( # TODO spec - add as helper? attestations.filterIt( it.data.epoch_boundary_root == boundary_hash and - it.data.justified_slot == state.justified_slot) + it.data.justified_epoch == state.justified_epoch) func lowerThan(candidate, current: Eth2Digest): bool = # return true iff candidate is "lower" than current, per spec rule: @@ -458,6 +458,10 @@ func processEpoch(state: var BeaconState) = base_reward_quotient = BASE_REWARD_QUOTIENT * integer_squareroot(total_balance_in_eth) + current_epoch = get_current_epoch(state) + previous_epoch = if current_epoch > GENESIS_EPOCH: current_epoch - 1 else: current_epoch + next_epoch = (current_epoch + 1).EpochNumber + func base_reward(state: BeaconState, index: Uint24): uint64 = get_effective_balance(state, index) div base_reward_quotient.uint64 div 4 @@ -499,7 +503,7 @@ func processEpoch(state: var BeaconState) = let # Previous epoch justified previous_epoch_justified_attestations = concat(current_epoch_attestations, previous_epoch_attestations). - filterIt(it.data.justified_slot == state.previous_justified_slot) + filterIt(it.data.justified_epoch == state.previous_justified_epoch) previous_epoch_justified_attester_indices = get_attester_indices(state, previous_epoch_justified_attestations) @@ -592,50 +596,55 @@ func processEpoch(state: var BeaconState) = break state.eth1_data_votes = @[] + # TODO Eth1 data + # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#eth1-data-1 + + # Helpers for justification + let + previous_total_balance = sum_effective_balances(state, get_active_validator_indices(state.validator_registry, previous_epoch)) + current_total_balance = sum_effective_balances(state, get_active_validator_indices(state.validator_registry, current_epoch)) + block: # Justification - state.previous_justified_slot = state.justified_slot + # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#justification - # TODO where's that bitfield type when you need it? - # TODO why are all bits kept? + # First, update the justification bitfield + var new_justified_epoch = state.justified_epoch state.justification_bitfield = state.justification_bitfield shl 1 + if 3'u64 * previous_epoch_boundary_attesting_balance >= 2'u64 * previous_total_balance: + state.justification_bitfield = state.justification_bitfield or 2 + new_justified_epoch = previous_epoch + if 3'u64 * current_epoch_boundary_attesting_balance >= 2'u64 * current_total_balance: + state.justification_bitfield = state.justification_bitfield or 1 + new_justified_epoch = current_epoch - # TODO Spec - underflow - if state.slot >= 2'u64 * EPOCH_LENGTH: - if 3'u64 * previous_epoch_boundary_attesting_balance >= - 2'u64 * total_balance: - state.justification_bitfield = state.justification_bitfield or 2 - state.justified_slot = state.slot - 2 * EPOCH_LENGTH + # Next, update last finalized epoch if possible + if (state.justification_bitfield shr 1) mod 8 == 0b111 and state.previous_justified_epoch == previous_epoch - 2: + state.finalized_epoch = state.previous_justified_epoch + if (state.justification_bitfield shr 1) mod 4 == 0b11 and state.previous_justified_epoch == previous_epoch - 1: + state.finalized_epoch = state.previous_justified_epoch + if (state.justification_bitfield shr 0) mod 8 == 0b111 and state.justified_epoch == previous_epoch - 1: + state.finalized_epoch = state.justified_epoch + if (state.justification_bitfield shr 0) mod 4 == 0b11 and state.justified_epoch == previous_epoch: + state.finalized_epoch = state.justified_epoch - if 3'u64 * current_epoch_boundary_attesting_balance >= - 2'u64 * total_balance: - state.justification_bitfield = state.justification_bitfield or 1 - state.justified_slot = state.slot - 1 * EPOCH_LENGTH - - block: # Finalization - if - (state.previous_justified_slot + 2 * EPOCH_LENGTH == state.slot and - state.justification_bitfield mod 4 == 3) or - (state.previous_justified_slot + 3 * EPOCH_LENGTH == state.slot and - state.justification_bitfield mod 8 == 7) or - (state.previous_justified_slot + 4 * EPOCH_LENGTH == state.slot and - state.justification_bitfield mod 16 in [15'u64, 14]): - state.finalized_slot = state.justified_slot + # Finally, update the following + state.previous_justified_epoch = state.justified_epoch + state.justified_epoch = new_justified_epoch block: # Crosslinks - 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) + # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#crosslinks + for slot in get_epoch_start_slot(previous_epoch) ..< get_epoch_start_slot(next_epoch): + 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)) + # TODO Rewards and penalties helpers + block: # Justification and finalization - let - epochs_since_finality = - (state.slot - state.finalized_slot) div EPOCH_LENGTH + let epochs_since_finality = next_epoch - state.finalized_epoch proc update_balance(attesters: openArray[Uint24], attesting_balance: uint64) = # TODO Spec - add helper? @@ -692,7 +701,8 @@ func processEpoch(state: var BeaconState) = base_reward(state, v) div INCLUDER_REWARD_QUOTIENT block: # Crosslinks - for slot in state.slot - 2 * EPOCH_LENGTH ..< state.slot - EPOCH_LENGTH: + # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#crosslinks-1 + for slot in get_epoch_start_slot(previous_epoch) ..< get_epoch_start_slot(current_epoch): let crosslink_committees_at_slot = get_crosslink_committees_at_slot(state, slot) for crosslink_committee in crosslink_committees_at_slot: # TODO https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#crosslinks-1 @@ -707,26 +717,29 @@ func processEpoch(state: var BeaconState) = block: # Ejections process_ejections(state) - block: # Validator registry - # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#validator-registry - state.previous_epoch_calculation_slot = state.current_epoch_calculation_slot + block: # Validator registry and shuffling seed data + # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#validator-registry-and-shuffling-seed-data + state.previous_calculation_epoch = state.current_calculation_epoch state.previous_epoch_start_shard = state.current_epoch_start_shard - state.previous_epoch_randao_mix = state.current_epoch_randao_mix + state.previous_epoch_seed = state.current_epoch_seed + #TODO state.latest_index_roots[next_epoch mod LATEST_INDEX_ROOTS_LENGTH] = hash_tree_root_final(get_active_validator_indices(state.validator_registry, next_epoch)) - if state.finalized_slot > state.validator_registry_update_slot and + if state.finalized_epoch > state.validator_registry_update_epoch and allIt( - 0 ..< get_current_epoch_committee_count_per_slot(state).int * EPOCH_LENGTH, - state.latest_crosslinks[(state.current_epoch_start_shard + it.uint64) mod SHARD_COUNT].slot > state.validator_registry_update_slot): + 0 ..< get_current_epoch_committee_count(state).int * EPOCH_LENGTH, + state.latest_crosslinks[(state.current_epoch_start_shard + it.uint64) mod SHARD_COUNT].epoch > state.validator_registry_update_epoch): update_validator_registry(state) - state.current_epoch_calculation_slot = state.slot - state.current_epoch_start_shard = (state.current_epoch_start_shard + get_current_epoch_committee_count_per_slot(state) * EPOCH_LENGTH) mod SHARD_COUNT - state.current_epoch_randao_mix = get_randao_mix(state, state.current_epoch_calculation_slot - SEED_LOOKAHEAD) + + state.current_epoch_start_shard = (state.current_epoch_start_shard + get_current_epoch_committee_count(state) * EPOCH_LENGTH) mod SHARD_COUNT + state.current_epoch_seed = generate_seed(state, state.current_calculation_epoch) + state.current_calculation_epoch = next_epoch else: # If a validator registry change does NOT happen - let epochs_since_last_registry_change = (state.slot - state.validator_registry_update_slot) div EPOCH_LENGTH + let epochs_since_last_registry_change = current_epoch - state.validator_registry_update_epoch if is_power_of_2(epochs_since_last_registry_change): - state.current_epoch_calculation_slot = state.slot - state.current_epoch_randao_mix = get_randao_mix(state, state.current_epoch_calculation_slot - SEED_LOOKAHEAD) + state.current_epoch_seed = generate_seed(state, state.current_calculation_epoch) + state.current_calculation_epoch = next_epoch + # /Note/ that state.current_epoch_start_shard is left unchanged # TODO run process_penalties_and_exits block: # Final updates diff --git a/beacon_chain/sync_protocol.nim b/beacon_chain/sync_protocol.nim index df220ab86..43b4a3c89 100644 --- a/beacon_chain/sync_protocol.nim +++ b/beacon_chain/sync_protocol.nim @@ -85,7 +85,7 @@ proc applyValidatorChangeLog*(log: ChangeLog, # 4. Apply all changes to the validator set # - outBeaconState.finalized_slot = + outBeaconState.finalized_epoch = log.signedBlock.slot div EPOCH_LENGTH outBeaconState.validator_registry_delta_chain_tip = diff --git a/beacon_chain/time.nim b/beacon_chain/time.nim index 0a8d82631..5caa95969 100644 --- a/beacon_chain/time.nim +++ b/beacon_chain/time.nim @@ -38,7 +38,7 @@ proc randomTimeInSlot*(s: BeaconState, proc slotDistanceFromNow*(s: BeaconState): int64 = ## Returns how many slots have passed since a particular BeaconState was finalized - int64(s.timeSinceGenesis() div (SLOT_DURATION * 1000)) - int64(s.finalized_slot) + int64(s.timeSinceGenesis() div (SLOT_DURATION * EPOCH_LENGTH * 1000)) - int64(s.finalized_epoch) proc synchronizeClock*() {.async.} = ## This should determine the offset of the local clock against a global diff --git a/tests/test_state_transition.nim b/tests/test_state_transition.nim index 43968b37d..e42df4a83 100644 --- a/tests/test_state_transition.nim +++ b/tests/test_state_transition.nim @@ -109,7 +109,7 @@ suite "Block processing": # Some time needs to pass before attestations are included - this is # to let the attestation propagate properly to interested participants - while state.slot < MIN_ATTESTATION_INCLUSION_DELAY + 1: + while state.slot < GENESIS_SLOT + MIN_ATTESTATION_INCLUSION_DELAY + 1: discard updateState( state, previous_block_root, none(BeaconBlock), {}) diff --git a/tests/test_validator.nim b/tests/test_validator.nim index 90372fa0c..536edff39 100644 --- a/tests/test_validator.nim +++ b/tests/test_validator.nim @@ -25,12 +25,12 @@ suite "Validators": num_validators = 32*1024 validators = repeat( Validator( - exit_slot: FAR_FUTURE_SLOT + exit_epoch: FAR_FUTURE_EPOCH ), num_validators) s = get_shuffling(Eth2Digest(), validators, 0) - committees = EPOCH_LENGTH * get_committee_count_per_slot(len(validators)).int + committees = get_epoch_committee_count(len(validators)).int check: s.len == committees # 32k validators: EPOCH_LENGTH slots * committee_count_per_slot = - # EPOCH_LENGTH * committee_count_per_slot committees. + # get_epoch_committee_count committees. sumCommittees(s, num_validators div committees) == validators.len() # all validators accounted for diff --git a/tests/testutil.nim b/tests/testutil.nim index 9778f194e..6af58afd6 100644 --- a/tests/testutil.nim +++ b/tests/testutil.nim @@ -152,7 +152,7 @@ proc addBlock*( assert bls_verify( proposer.pubkey, proposal_hash, new_block.signature, - get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL)), + get_domain(state.fork, state.slot, DOMAIN_PROPOSAL)), "we just signed this message - it should pass verification!" new_block @@ -189,8 +189,8 @@ proc makeAttestation*( epoch_boundary_root: Eth2Digest(), # TODO shard_block_root: Eth2Digest(), # TODO latest_crosslink_root: Eth2Digest(), # TODO - justified_slot: state.justified_slot, - justified_block_root: get_block_root(state, state.justified_slot), + justified_epoch: state.justified_epoch, + justified_block_root: get_block_root(state, get_epoch_start_slot(state.justified_epoch)), ) assert sac_index != -1, "find_shard_committe should guarantee this"