From 2fc3f8879546d570a9b81da8ee21332420ac985b Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 5 Dec 2018 11:22:15 +0000 Subject: [PATCH] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 204 ++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 97 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 19d1db894..4e0925571 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -70,25 +70,26 @@ - [Routine for activating a validator](#routine-for-activating-a-validator) - [Routine for exiting a validator](#routine-for-exiting-a-validator) - [Per-slot processing](#per-slot-processing) - - [Verify attestations](#verify-attestations) - - [Verify proposer signature](#verify-proposer-signature) - - [Process RANDAO](#process-randao) - - [Process PoW receipt root](#process-pow-receipt-root) - - [Process special objects](#process-special-objects) + - [Proposer signature](#proposer-signature) + - [Attestations](#attestations) + - [RANDAO](#randao) + - [PoW receipt root](#pow-receipt-root) + - [Special objects](#special-objects) - [`VOLUNTARY_EXIT`](#voluntary_exit) - [`CASPER_SLASHING`](#casper_slashing) - [`PROPOSER_SLASHING`](#proposer_slashing) - [`DEPOSIT_PROOF`](#deposit_proof) - [Per-epoch processing](#per-epoch-processing) - - [Precomputation](#precomputation) - - [Adjust justified slots and crosslink status](#adjust-justified-slots-and-crosslink-status) - - [Balance recalculations related to FFG rewards](#balance-recalculations-related-to-ffg-rewards) - - [Balance recalculations related to crosslink rewards](#balance-recalculations-related-to-crosslink-rewards) - - [Receipt root voting](#receipt-root-voting) - - [Validator registry change](#validator-registry-change) - - [If a validator registry change does NOT happen](#if-a-validator-registry-change-does-not-happen) + - [Helpers](#helpers) + - [Receipt roots](#receipt-roots) + - [Justification](#justification) + - [Finalization](#finalization) + - [Crosslinks](#crosslinks) + - [Justification and finalization rewards and penalties](#justification-and-finalization-rewards-and-penalties) + - [Crosslink rewards and penalties](#crosslink-rewards-and-penalties) + - [Validator registry](#validator-registry) - [Proposer reshuffling](#proposer-reshuffling) - - [Finally...](#finally) + - [Final updates](#final-updates) - [Appendix](#appendix) - [Appendix A - Hash function](#appendix-a---hash-function) - [References](#references) @@ -142,7 +143,6 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted | `BLS_WITHDRAWAL_CREDENTIALS` | `0x00` | - | * For the safety of crosslinks a minimum committee size of 111 is [recommended](https://vitalik.ca/files/Ithaca201807_Sharding.pdf). (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) The shuffling algorithm generally ensures (assuming sufficient validators) committee sizes at least `TARGET_COMMITTEE_SIZE // 2`. -* At most `1/MAX_BALANCE_CHURN_QUOTIENT` of the [validators](#dfn-validator) can change during each [validator](#dfn-validator) registry change. ### Deposit contract @@ -381,7 +381,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'withdrawal_credentials': 'hash32', # RANDAO commitment 'randao_commitment': 'hash32', - # Slots the proposer has skipped (ie. layers of RANDAO expected) + # Slots the proposer has skipped (i.e. layers of RANDAO expected) 'randao_skips': 'uint64', # Balance in Gwei 'balance': 'uint64', @@ -642,7 +642,7 @@ For a beacon chain block, `block`, to be processed by a node, the following cond If these conditions are not met, the client should delay processing the beacon block until the conditions are all satisfied. -Beacon block production is significantly different because of the proof of stake mechanism. A client simply checks what it thinks is the canonical chain when it should create a block, and looks up what its slot number is; when the slot arrives, it either proposes or attests to a block as required. Note that this requires each node to have a clock that is roughly (ie. within `SLOT_DURATION` seconds) synchronized with the other nodes. +Beacon block production is significantly different because of the proof of stake mechanism. A client simply checks what it thinks is the canonical chain when it should create a block, and looks up what its slot number is; when the slot arrives, it either proposes or attests to a block as required. Note that this requires each node to have a clock that is roughly (i.e. within `SLOT_DURATION` seconds) synchronized with the other nodes. ### Beacon chain fork choice rule @@ -940,7 +940,7 @@ def integer_squareroot(n: int) -> int: ### On startup -A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the following values. Other validity rules (eg. requiring a signature) do not apply. +A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the following values. Other validity rules (e.g. requiring a signature) do not apply. ```python { @@ -1185,9 +1185,9 @@ def exit_validator(index: int, ## Per-slot processing -Below are the steps that happen at every slot. Denote by `block` the associated block, possibly a "skip block". +Below are the processing steps that happen at every slot. Denote by `block` the associated block, possibly a skip block. -* Let `parent_hash` be the hash of the immediate previous beacon slot (ie. equal to `block.ancestor_hashes[0]`). +* Let `parent_hash` be the hash of the immediate previous beacon slot (i.e. equal to `block.ancestor_hashes[0]`). * Let `parent` be the beacon slot with the hash `parent_hash`. First, set `state.latest_block_hashes` to the output of the following: @@ -1214,7 +1214,13 @@ def update_ancestor_hashes(parent_ancestor_hashes: List[Hash32], return new_ancestor_hashes ``` -### Verify attestations +### Proposer signature + +* Let `block_hash_without_sig` be the hash of `block` where `proposer_signature` is set to `[0, 0]`. +* Let `proposal_hash = hash(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`. +* Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))`. + +### Attestations * Verify that `len(block.attestations) <= MAX_ATTESTATIONS_PER_BLOCK`. @@ -1232,13 +1238,7 @@ For each `attestation` in `block.attestations`: * [TO BE REMOVED IN PHASE 1] Verify that `shard_block_hash == ZERO_HASH`. * Append `PendingAttestationRecord(data=attestation.data, participation_bitfield=attestation.participation_bitfield, custody_bitfield=attestation.custody_bitfield, slot_included=state.slot)` to `state.latest_attestations`. -### Verify proposer signature - -* Let `block_hash_without_sig` be the hash of `block` where `proposer_signature` is set to `[0, 0]`. -* Let `proposal_hash = hash(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`. -* Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))`. - -### Process RANDAO +### RANDAO If `block` is a skip block: @@ -1253,11 +1253,11 @@ If `block` is not a skip block: * Set `proposer.randao_commitment = block.randao_reveal`. * Set `proposer.randao_skips = 0`. -### Process PoW receipt root +### PoW receipt root If `block.candidate_pow_receipt_root` is `x.candidate_pow_receipt_root` for some `x` in `state.candidate_pow_receipt_roots`, set `x.votes += 1`. Otherwise, append to `state.candidate_pow_receipt_roots` a new `CandidatePoWReceiptRootRecord(candidate_pow_receipt_root=block.candidate_pow_receipt_root, votes=1)`. -### Process special objects +### Special objects * Verify that the quantity of each type of object in `block.specials` is less than or equal to its maximum (see table at the top). * Verify that objects are sorted in order of `kind`. That is, `block.specials[i+1].kind >= block.specials[i].kind` for `0 <= i < len(block.specials-1)`. @@ -1344,13 +1344,15 @@ process_deposit( The steps below happen when `state.slot % EPOCH_LENGTH == 0`. For simplicity we denote `state.slot - EPOCH_LENGTH` by `s`. -### Precomputation +### Helpers All [validators](#dfn-validator): * Let `active_validators = [state.validator_registry[i] for i in get_active_validator_indices(state.validator_registry)]`. -* Let `total_balance = sum([get_effective_balance(v) for v in active_validators])`. Let `total_balance_in_eth = total_balance // GWEI_PER_ETH`. +* Let `total_balance = sum([get_effective_balance(v) for v in active_validators])`. +* Let `total_balance_in_eth = total_balance // GWEI_PER_ETH`. * Let `reward_quotient = BASE_REWARD_QUOTIENT * integer_squareroot(total_balance_in_eth)`. (The per-slot maximum interest rate is `2/reward_quotient`.) +* Let `base_reward(v) = get_effective_balance(v) // reward_quotient` for any validator `v`. [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch: @@ -1374,30 +1376,46 @@ For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots`: * Let `winning_hash(obj)` be the winning `shard_block_hash` value. * Let `total_balance(obj) = sum([get_effective_balance(v) for v in obj.committee])`. -Let `inclusion_slot(v)` equal `a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`, and `inclusion_distance(v) = a.slot_included - a.data.slot` for the same attestation. We define a function `adjust_for_inclusion_distance(magnitude, distance)` which adjusts the reward of an attestation based on how long it took to get included (the longer, the lower the reward). Returns a value between 0 and `magnitude`. +* Let `inclusion_slot(v)` equal `a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`, and `inclusion_distance(v) = a.slot_included - a.data.slot` for the same attestation. +* Let `adjust_for_inclusion_distance(magnitude, distance)` be the function below. ```python def adjust_for_inclusion_distance(magnitude: int, distance: int) -> int: + """ + Adjusts the reward of an attestation based on how long it took to get included (the longer, the lower the reward). + Returns a value between ``0`` and ``magnitude``. + "" return magnitude // 2 + (magnitude // 2) * MIN_ATTESTATION_INCLUSION_DELAY // distance ``` -For any [validator](#dfn-validator) `v`, let `base_reward(v) = get_effective_balance(v) // reward_quotient`. +### Receipt roots -### Adjust justified slots and crosslink status +If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then: + +* Set `state.processed_pow_receipt_root = x.receipt_root` if `x.votes * 2 > POW_RECEIPT_ROOT_VOTING_PERIOD` for any `x` in `state.candidate_pow_receipt_root`. +* Set `state.candidate_pow_receipt_roots = []`. + +### Justification * Set `state.justification_bitfield = (state.justification_bitfield * 2) % 2**64`. -* If `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 2` (ie. flip the second lowest bit to 1) and `new_justified_slot = s - EPOCH_LENGTH`. -* If `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 1` (ie. flip the lowest bit to 1) and `new_justified_slot = s`. -* If `state.justified_slot == s - EPOCH_LENGTH and state.justification_bitfield % 4 == 3`, set `state.finalized_slot = state.justified_slot`. -* If `state.justified_slot == s - EPOCH_LENGTH - EPOCH_LENGTH and state.justification_bitfield % 8 == 7`, set `state.finalized_slot = state.justified_slot`. -* If `state.justified_slot == s - EPOCH_LENGTH - 2 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`, set `state.finalized_slot = state.justified_slot`. -* Set `state.previous_justified_slot = state.justified_slot` and if `new_justified_slot` has been set, set `state.justified_slot = new_justified_slot`. +* If `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 2` (i.e. set the second lowest bit to 1) and `new_justified_slot = s - EPOCH_LENGTH`. +* If `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance` then set `state.justification_bitfield &= 1` (i.e. set the lowest bit to 1) and `new_justified_slot = s`. +* Set `state.previous_justified_slot = state.justified_slot`. +* Set `state.justified_slot = new_justified_slot` if `new_justified_slot` has been set. -For every `ShardCommittee` object `obj`: +### Finalization -* If `3 * total_attesting_balance(obj) >= 2 * total_balance(obj)`, set `crosslinks[shard] = CrosslinkRecord(slot=state.slot, hash=winning_hash(obj))`. +* Set `state.finalized_slot = state.justified_slot` if `state.justified_slot == s - 1 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3`. +* Set `state.finalized_slot = state.justified_slot` if `state.justified_slot == s - 2 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. +* Set `state.finalized_slot = state.justified_slot` If `state.justified_slot == s - 3 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. -### Balance recalculations related to FFG rewards +### Crosslinks + +For every `shard_committee` in `state.shard_committees_at_slots`: + +* Set `crosslinks[shard] = CrosslinkRecord(slot=state.slot, hash=winning_hash(shard_committee))` if `3 * total_attesting_balance(shard_committee) >= 2 * total_balance(shard_committee)`. + +### Justification and finalization rewards and penalties Note: When applying penalties in the following balance recalculations implementers should make sure the `uint64` does not underflow. @@ -1415,41 +1433,45 @@ Case 2: `time_since_finality > 4 * EPOCH_LENGTH`: For each `v` in `previous_epoch_boundary_attesters`, we determine the proposer `proposer_index = get_beacon_proposer_index(state, inclusion_slot(v))` and set `state.validator_registry[proposer_index].balance += base_reward(v) // INCLUDER_REWARD_QUOTIENT`. -### Balance recalculations related to crosslink rewards +### Crosslink rewards and penalties -For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots[:EPOCH_LENGTH]` (ie. the objects corresponding to the epoch before the current one), for each `v` in `[state.validator_registry[index] for index in obj.committee]`, adjust balances as follows: +For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots[:EPOCH_LENGTH]` (i.e. the objects corresponding to the epoch before the current one), for each `v` in `[state.validator_registry[index] for index in obj.committee]`, adjust balances as follows: * If `v in attesting_validators(obj)`, `v.balance += adjust_for_inclusion_distance(base_reward(v) * total_attesting_balance(obj) // total_balance(obj)), inclusion_distance(v))`. * If `v not in attesting_validators(obj)`, `v.balance -= base_reward(v)`. -### Receipt root voting +### Validator registry -If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then: - -* Set `state.processed_pow_receipt_root = x.receipt_root` if `x.votes * 2 > POW_RECEIPT_ROOT_VOTING_PERIOD` for any `x` in `state.candidate_pow_receipt_root`. -* Set `state.candidate_pow_receipt_roots = []`. - -### Validator registry change - -A [validator](#dfn-validator) registry change occurs if the following criteria are satisfied: +If the following are satisfied: * `state.finalized_slot > state.validator_registry_latest_change_slot` * `crosslinks[shard].slot > state.validator_registry_latest_change_slot` for every shard number `shard` in `state.shard_committees_at_slots` -A helper function is defined as: +update the validator registry by running ```python -def get_changed_validators(validators: List[ValidatorRecord], - latest_penalized_exit_balances: List[int], - validator_registry_delta_chain_tip: int, - current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]: + update_validator_registry( + copy.deepcopy(state.validator_registry), + copy.deepcopy(state.latest_penalized_exit_balances), + state.validator_registry_delta_chain_tip, + state.slot + ) +``` + +where + +```python +def update_validator_registry(validator_registry: List[ValidatorRecord], + latest_penalized_exit_balances: List[int], + validator_registry_delta_chain_tip: int, + current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]: """ - Return changed validator registry and ``latest_penalized_exit_balances``, ``validator_registry_delta_chain_tip``. + Update the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``. """ # The active validators - active_validator_indices = get_active_validator_indices(validators) + active_validator_indices = get_active_validator_indices(validator_registry) # The total effective balance of active validators - total_balance = sum([get_effective_balance(v) for i, v in enumerate(validators) if i in active_validator_indices]) + total_balance = sum([get_effective_balance(v) for i, v in enumerate(validator_registry) if i in active_validator_indices]) # The maximum balance churn in Gwei (for deposits and exits separately) max_balance_churn = max( @@ -1459,39 +1481,41 @@ def get_changed_validators(validators: List[ValidatorRecord], # Activate validators within the allowable balance churn balance_churn = 0 - for i in range(len(validators)): - if validators[i].status == PENDING_ACTIVATION and validators[i].balance >= MAX_DEPOSIT: + for i in range(len(validator_registry)): + validator = validator_registry[i] + if validator.status == PENDING_ACTIVATION and validator.balance >= MAX_DEPOSIT: # Check the balance churn would be within the allowance - balance_churn += get_effective_balance(validators[i]) + balance_churn += get_effective_balance(validator) if balance_churn > max_balance_churn: break # Activate validator - validators[i].status = ACTIVE - validators[i].latest_status_change_slot = current_slot + validator.status = ACTIVE + validator.latest_status_change_slot = current_slot validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( validator_registry_delta_chain_tip=validator_registry_delta_chain_tip, index=i, - pubkey=validators[i].pubkey, + pubkey=validator.pubkey, flag=ACTIVATION, ) # Exit validators within the allowable balance churn balance_churn = 0 for i in range(len(validators)): - if validators[i].status == ACTIVE_PENDING_EXIT: + validator = validator_registry[i] + if validator.status == ACTIVE_PENDING_EXIT: # Check the balance churn would be within the allowance - balance_churn += get_effective_balance(validators[i]) + balance_churn += get_effective_balance(validator) if balance_churn > max_balance_churn: break # Exit validator - validators[i].status = EXITED_WITHOUT_PENALTY - validators[i].latest_status_change_slot = current_slot + validator.status = EXITED_WITHOUT_PENALTY + validator.latest_status_change_slot = current_slot validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( validator_registry_delta_chain_tip=validator_registry_delta_chain_tip, index=i, - pubkey=validators[i].pubkey, + pubkey=validator.pubkey, flag=EXIT, ) @@ -1506,38 +1530,24 @@ def get_changed_validators(validators: List[ValidatorRecord], # Calculate penalties for slashed validators def to_penalize(v): return v.status == EXITED_WITH_PENALTY - validators_to_penalize = filter(to_penalize, validators) + validators_to_penalize = filter(to_penalize, validator_registry) for v in validators_to_penalize: v.balance -= get_effective_balance(v) * min(total_penalties * 3, total_balance) // total_balance - return validators, latest_penalized_exit_balances, validator_registry_delta_chain_tip + # Update the state + state.validator_registry = validator_registry + state.validator_registry_delta_chain_tip = validator_registry_delta_chain_tip + state.latest_penalized_exit_balances = latest_penalized_exit_balances ``` -Then, run the following algorithm to update the [validator](#dfn-validator) registry: - -```python -def change_validators(state: BeaconState, - current_slot: int) -> None: - """ - Change validator registry. - Note that this function mutates ``state``. - """ - state.validator_registry, state.latest_penalized_exit_balances = get_changed_validators( - copy.deepcopy(state.validator_registry), - copy.deepcopy(state.latest_penalized_exit_balances), - state.validator_registry_delta_chain_tip, - current_slot - ) -``` - -And perform the following updates: +Also perform the following updates: * Set `state.validator_registry_latest_change_slot = state.slot`. * Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`. * Set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.next_seed, state.validator_registry, next_start_shard)` where next_start_shard = (state.shard_committees_at_slots[-1][-1].shard + 1) % SHARD_COUNT`. * Set `state.next_seed = state.randao_mix`. -### If a validator registry change does NOT happen +If a validator registry update does _not_ happen do the following: * Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`. * Let `time_since_finality = state.slot - state.validator_registry_latest_change_slot`. @@ -1571,10 +1581,10 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com state.persistent_committees[reassignment.shard].append(reassignment.validator_index) ``` -### Finally... +### Final updates -* Remove all attestation records older than slot `s`. -* For any [validator](#dfn-validator) with index `i` with balance less than `MIN_BALANCE` and status `ACTIVE`, run `exit_validator(i, state, penalize=False, current_slot=state.slot)`. +* Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < s`. +* Run `exit_validator(i, state, penalize=False, current_slot=state.slot)` for indices `i` such that `state.validator_registry[i].status = ACTIVE and state.validator_registry[i].balance < MIN_BALANCE`. * Set `state.latest_block_hashes = state.latest_block_hashes[EPOCH_LENGTH:]`. # Appendix