From 4c0841ec6add26406d7e2c2bf56fead41c8b4ff8 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 4 Dec 2018 21:28:31 +0000 Subject: [PATCH 01/21] Move to a per-slot state transition function Initial pass for the migration from a per-block state transition function to a per-slot state transition function. More simplifications and cleanups can be made. --- specs/core/0_beacon-chain.md | 164 +++++++++++++++++------------------ 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3a1a66ea5..19d1db894 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -69,22 +69,22 @@ - [On startup](#on-startup) - [Routine for activating a validator](#routine-for-activating-a-validator) - [Routine for exiting a validator](#routine-for-exiting-a-validator) - - [Per-block processing](#per-block-processing) + - [Per-slot processing](#per-slot-processing) - [Verify attestations](#verify-attestations) - [Verify proposer signature](#verify-proposer-signature) - - [Verify and process the RANDAO reveal](#verify-and-process-the-randao-reveal) + - [Process RANDAO](#process-randao) - [Process PoW receipt root](#process-pow-receipt-root) - [Process special objects](#process-special-objects) - [`VOLUNTARY_EXIT`](#voluntary_exit) - [`CASPER_SLASHING`](#casper_slashing) - [`PROPOSER_SLASHING`](#proposer_slashing) - [`DEPOSIT_PROOF`](#deposit_proof) - - [Epoch boundary processing](#epoch-boundary-processing) + - [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) - - [Ethereum 1.0 chain related rules](#ethereum-10-chain-related-rules) + - [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) - [Proposer reshuffling](#proposer-reshuffling) @@ -334,11 +334,16 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted ```python { + # Misc + 'slot': 'uint64', + 'genesis_time': 'uint64', + 'fork_data': ForkData, # For versioning hard forks + # Validator registry 'validator_registry': [ValidatorRecord], 'validator_registry_latest_change_slot': 'uint64', 'validator_registry_exit_count': 'uint64', - 'validator_registry_delta_chain_tip': 'hash32', # For light clients to easily track delta + 'validator_registry_delta_chain_tip': 'hash32', # For light clients to track deltas # Randomness and committees 'randao_mix': 'hash32', @@ -363,10 +368,6 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted # PoW receipt root 'processed_pow_receipt_root': 'hash32', 'candidate_pow_receipt_roots': [CandidatePoWReceiptRootRecord], - - # Misc - 'genesis_time': 'uint64', - 'fork_data': ForkData, # For versioning hard forks } ``` @@ -629,13 +630,13 @@ The beacon chain is the system chain for Ethereum 2.0. The main responsibilities * Store and maintain the registry of [validators](#dfn-validator) * Process crosslinks (see above) -* Process its own block-by-block consensus, as well as the finality gadget +* Process its per-slot consensus, as well as the finality gadget -Processing the beacon chain is fundamentally similar to processing the Ethereum 1.0 chain in many respects. Clients download and process blocks, and maintain a view of what is the current "canonical chain", terminating at the current "head". However, because of the beacon chain's relationship with Ethereum 1.0, and because it is a proof-of-stake chain, there are differences. +Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Clients download and process blocks, and maintain a view of what is the current "canonical chain", terminating at the current "head". However, because of the beacon chain's relationship with Ethereum 1.0, and because it is a proof-of-stake chain, there are differences. For a beacon chain block, `block`, to be processed by a node, the following conditions must be met: -* The parent block, `block.ancestor_hashes[0]`, has been processed and accepted. +* The parent block (possibly a skip block) with hash `block.ancestor_hashes[0]` has been processed and accepted. * The Ethereum 1.0 block pointed to by the `state.processed_pow_receipt_root` has been processed and accepted. * The node's local clock time is greater than or equal to `state.genesis_time + block.slot * SLOT_DURATION`. @@ -678,10 +679,10 @@ def lmd_ghost(store, start): We now define the state transition function. At a high level the state transition is made up of two parts: -1. The per-block processing, which happens every block, and only affects a few parts of the `state`. -2. The inter-epoch state recalculation, which happens only if `block.slot >= state.latest_state_recalculation_slot + EPOCH_LENGTH`, and affects the entire `state`. +1. The per-slot transitions, which happens every slot, and only affects a parts of the `state`. +2. The per-epoch transitions, which happens at every epoch boundary (i.e. `state.slot % EPOCH_LENGTH == 0`), and affects the entire `state`. -The inter-epoch state recalculation generally focuses on changes to the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization, while the per-block processing generally focuses on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. +The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization. The per-slot transitions generally focuses on verifying aggregate signatures and saving temporary records relating to the per-slot activity in the `BeaconState`. ### Helper functions @@ -982,37 +983,44 @@ def on_startup(initial_validator_entries: List[Any], # Setup state initial_shuffling = get_new_shuffling(ZERO_HASH, initial_validator_registry, 0) state = BeaconState( - validator_registry=initial_validator_registry, - validator_registry_latest_change_slot=INITIAL_SLOT_NUMBER, - validator_registry_exit_count=0, - validator_registry_delta_chain_tip=ZERO_HASH, - # Randomness and committees - randao_mix=ZERO_HASH, - next_seed=ZERO_HASH, - shard_committees_at_slots=initial_shuffling + initial_shuffling, - persistent_committees=split(shuffle(initial_validator_registry, ZERO_HASH), SHARD_COUNT), - persistent_committee_reassignments=[], - # Finality - previous_justified_slot=INITIAL_SLOT_NUMBER, - justified_slot=INITIAL_SLOT_NUMBER, - justification_bitfield=0, - finalized_slot=INITIAL_SLOT_NUMBER, - # Recent state - latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, hash=ZERO_HASH) for _ in range(SHARD_COUNT)], - latest_state_recalculation_slot=INITIAL_SLOT_NUMBER, - latest_block_hashes=[ZERO_HASH for _ in range(EPOCH_LENGTH * 2)], - latest_penalized_exit_balances=[], - latest_attestations=[], - # PoW receipt root - processed_pow_receipt_root=processed_pow_receipt_root, - candidate_pow_receipt_roots=[], # Misc + slot=INITIAL_SLOT_NUMBER, genesis_time=genesis_time, fork_data=ForkData( pre_fork_version=INITIAL_FORK_VERSION, post_fork_version=INITIAL_FORK_VERSION, fork_slot=INITIAL_SLOT_NUMBER, ), + + # Validator registry + validator_registry=initial_validator_registry, + validator_registry_latest_change_slot=INITIAL_SLOT_NUMBER, + validator_registry_exit_count=0, + validator_registry_delta_chain_tip=ZERO_HASH, + + # Randomness and committees + randao_mix=ZERO_HASH, + next_seed=ZERO_HASH, + shard_committees_at_slots=initial_shuffling + initial_shuffling, + persistent_committees=split(shuffle(initial_validator_registry, ZERO_HASH), SHARD_COUNT), + persistent_committee_reassignments=[], + + # Finality + previous_justified_slot=INITIAL_SLOT_NUMBER, + justified_slot=INITIAL_SLOT_NUMBER, + justification_bitfield=0, + finalized_slot=INITIAL_SLOT_NUMBER, + + # Recent state + latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, hash=ZERO_HASH) for _ in range(SHARD_COUNT)], + latest_state_recalculation_slot=INITIAL_SLOT_NUMBER, + latest_block_hashes=[ZERO_HASH for _ in range(EPOCH_LENGTH * 2)], + latest_penalized_exit_balances=[], + latest_attestations=[], + + # PoW receipt root + processed_pow_receipt_root=processed_pow_receipt_root, + candidate_pow_receipt_roots=[], ) return state @@ -1175,12 +1183,12 @@ def exit_validator(index: int, ) ``` -## Per-block processing +## Per-slot processing -This procedure should be carried out for every beacon block (denoted `block`). +Below are the 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 block (ie. equal to `block.ancestor_hashes[0]`). -* Let `parent` be the beacon block with the hash `parent_hash`. +* Let `parent_hash` be the hash of the immediate previous beacon slot (ie. 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: @@ -1212,7 +1220,7 @@ def update_ancestor_hashes(parent_ancestor_hashes: List[Hash32], For each `attestation` in `block.attestations`: -* Verify that `attestation.data.slot <= block.slot - MIN_ATTESTATION_INCLUSION_DELAY`. +* Verify that `attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY`. * Verify that `attestation.data.slot >= max(parent.slot - EPOCH_LENGTH + 1, 0)`. * Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.latest_state_recalculation_slot else state.previous_justified_slot`. * Verify that `attestation.data.justified_block_hash` is equal to `get_block_hash(state, block, attestation.data.justified_slot)`. @@ -1222,28 +1230,24 @@ For each `attestation` in `block.attestations`: * Let `group_public_key = BLSAddPubkeys([state.validator_registry[v].pubkey for v in participants])`. * Verify that `BLSVerify(pubkey=group_public_key, msg=SSZTreeHash(attestation.data) + bytes1(0), sig=aggregate_signature, domain=get_domain(state.fork_data, slot, DOMAIN_ATTESTATION))`. * [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=block.slot)` to `state.latest_attestations`. +* 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(block.slot, BEACON_CHAIN_SHARD_NUMBER, block_hash_without_sig))`. -* Verify that `BLSVerify(pubkey=state.validator_registry[get_beacon_proposer_index(state, block.slot)].pubkey, data=proposal_hash, sig=block.proposer_signature, domain=get_domain(state.fork_data, block.slot, DOMAIN_PROPOSAL))`. +* 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))`. -### Verify and process the RANDAO reveal +### Process RANDAO -First run the following state transition to update `randao_skips` variables for the missing slots. +If `block` is a skip block: -```python -for slot in range(parent.slot + 1, block.slot): - proposer_index = get_beacon_proposer_index(state, slot) - state.validator_registry[proposer_index].randao_skips += 1 -``` +* Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. -Then: +If `block` is not a skip block: * Let `repeat_hash(x, n) = x if n == 0 else repeat_hash(hash(x), n-1)`. -* Let `proposer = state.validator_registry[get_beacon_proposer_index(state, block.slot)]`. +* Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`. * Verify that `repeat_hash(block.randao_reveal, proposer.randao_skips + 1) == proposer.randao_commitment`. * Set `state.randao_mix = xor(state.randao_mix, block.randao_reveal)`. * Set `proposer.randao_commitment = block.randao_reveal`. @@ -1269,9 +1273,9 @@ For each `special` in `block.specials`: * Let `validator = state.validator_registry[validator_index]`. * Verify that `BLSVerify(pubkey=validator.pubkey, msg=ZERO_HASH, sig=signature, domain=get_domain(state.fork_data, slot, DOMAIN_EXIT))`. * Verify that `validator.status == ACTIVE`. -* Verify that `block.slot >= slot`. -* Verify that `block.slot >= validator.latest_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD`. -* Run `exit_validator(validator_index, state, penalize=False, current_slot=block.slot)`. +* Verify that `state.slot >= slot`. +* Verify that `state.slot >= validator.latest_status_change_slot + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD`. +* Run `exit_validator(validator_index, state, penalize=False, current_slot=state.slot)`. #### `CASPER_SLASHING` @@ -1292,7 +1296,7 @@ def verify_special_attestation_data(state: State, obj: SpecialAttestationData) - * Verify that `len(intersection) >= 1`. * Verify that `vote_1.data.justified_slot + 1 < vote_2.data.justified_slot + 1 == vote_2.data.slot < vote_1.data.slot` or `vote_1.data.slot == vote_2.data.slot`. -For each [validator](#dfn-validator) index `i` in `intersection`, if `state.validator_registry[i].status` does not equal `EXITED_WITH_PENALTY`, then run `exit_validator(i, state, penalize=True, current_slot=block.slot)` +For each [validator](#dfn-validator) index `i` in `intersection`, if `state.validator_registry[i].status` does not equal `EXITED_WITH_PENALTY`, then run `exit_validator(i, state, penalize=True, current_slot=state.slot)` #### `PROPOSER_SLASHING` @@ -1301,7 +1305,7 @@ For each [validator](#dfn-validator) index `i` in `intersection`, if `state.vali * Verify that `proposal_data_1 != proposal_data_2`. * Verify that `proposal_data_1.slot == proposal_data_2.slot`. * Verify that `state.validator_registry[proposer_index].status != EXITED_WITH_PENALTY`. -* Run `exit_validator(proposer_index, state, penalize=True, current_slot=block.slot)`. +* Run `exit_validator(proposer_index, state, penalize=True, current_slot=state.slot)`. #### `DEPOSIT_PROOF` @@ -1320,7 +1324,7 @@ def verify_merkle_branch(leaf: Hash32, branch: [Hash32], depth: int, index: int, return value == root ``` -* Verify that `block.slot - (deposit_data.timestamp - state.genesis_time) // SLOT_DURATION < ZERO_BALANCE_VALIDATOR_TTL`. +* Verify that `state.slot - (deposit_data.timestamp - state.genesis_time) // SLOT_DURATION < ZERO_BALANCE_VALIDATOR_TTL`. * Run the following: ```python @@ -1332,15 +1336,13 @@ process_deposit( withdrawal_credentials=deposit_data.deposit_parameters.withdrawal_credentials, randao_commitment=deposit_data.deposit_parameters.randao_commitment, status=PENDING_ACTIVATION, - current_slot=block.slot + current_slot=state.slot ) ``` -## Epoch boundary processing +## Per-epoch processing -Repeat the steps in this section while `block.slot - state.latest_state_recalculation_slot >= EPOCH_LENGTH`. For simplicity, we use `s` as `state.latest_state_recalculation_slot`. - -Note that `state.latest_state_recalculation_slot` will always be a multiple of `EPOCH_LENGTH`. In the "happy case", this process will trigger, and loop once, every time `block.slot` passes a new exact multiple of `EPOCH_LENGTH`, but if a chain skips more than an entire epoch then the loop may run multiple times, incrementing `state.latest_state_recalculation_slot` by `EPOCH_LENGTH` with each iteration. +The steps below happen when `state.slot % EPOCH_LENGTH == 0`. For simplicity we denote `state.slot - EPOCH_LENGTH` by `s`. ### Precomputation @@ -1352,7 +1354,7 @@ All [validators](#dfn-validator): [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch: -* Let `this_epoch_attestations = [a for a in state.latest_attestations if s <= a.data.slot < s + EPOCH_LENGTH]`. (note: this is the set of attestations _of slots in the epoch `s...s+EPOCH_LENGTH-1`_, not attestations _that got included in the chain during the epoch `s...s+EPOCH_LENGTH-1`_) +* Let `this_epoch_attestations = [a for a in state.latest_attestations if s <= a.data.slot < s + EPOCH_LENGTH]`. (Note: this is the set of attestations of slots in the epoch `s...s+EPOCH_LENGTH-1`, _not_ attestations that got included in the chain during the epoch `s...s+EPOCH_LENGTH-1`.) * Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_hash(state, block, s) and a.justified_slot == state.justified_slot]`. * Let `this_epoch_boundary_attesters` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_boundary_attestations]`. * Let `this_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in this_epoch_boundary_attesters])`. @@ -1393,13 +1395,13 @@ For any [validator](#dfn-validator) `v`, let `base_reward(v) = get_effective_bal For every `ShardCommittee` object `obj`: -* If `3 * total_attesting_balance(obj) >= 2 * total_balance(obj)`, set `crosslinks[shard] = CrosslinkRecord(slot=state.latest_state_recalculation_slot + EPOCH_LENGTH, hash=winning_hash(obj))`. +* If `3 * total_attesting_balance(obj) >= 2 * total_balance(obj)`, set `crosslinks[shard] = CrosslinkRecord(slot=state.slot, hash=winning_hash(obj))`. ### Balance recalculations related to FFG rewards Note: When applying penalties in the following balance recalculations implementers should make sure the `uint64` does not underflow. -* Let `time_since_finality = block.slot - state.finalized_slot`. +* Let `time_since_finality = state.slot - state.finalized_slot`. Case 1: `time_since_finality <= 4 * EPOCH_LENGTH`: @@ -1420,19 +1422,19 @@ For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots[:EPO * 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)`. -### Ethereum 1.0 chain related rules +### Receipt root voting -If `state.latest_state_recalculation_slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then: +If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then: -* If for any `x` in `state.candidate_pow_receipt_root`, `x.votes * 2 >= POW_RECEIPT_ROOT_VOTING_PERIOD` set `state.processed_pow_receipt_root = x.receipt_root`. +* 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 all of the following criteria are satisfied: +A [validator](#dfn-validator) registry change occurs if the following criteria are satisfied: * `state.finalized_slot > state.validator_registry_latest_change_slot` -* For every shard number `shard` in `state.shard_committees_at_slots`, `crosslinks[shard].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: @@ -1528,18 +1530,17 @@ def change_validators(state: BeaconState, ) ``` -And perform the following updates to the `state`: +And perform the following updates: -* Set `state.validator_registry_latest_change_slot = s + EPOCH_LENGTH`. +* Set `state.validator_registry_latest_change_slot = state.slot`. * Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`. -* Let `next_start_shard = (state.shard_committees_at_slots[-1][-1].shard + 1) % SHARD_COUNT`. -* Set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.next_seed, state.validator_registry, next_start_shard)`. +* 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 * Set `state.shard_committees_at_slots[:EPOCH_LENGTH] = state.shard_committees_at_slots[EPOCH_LENGTH:]`. -* Let `time_since_finality = block.slot - state.validator_registry_latest_change_slot`. +* Let `time_since_finality = state.slot - state.validator_registry_latest_change_slot`. * Let `start_shard = state.shard_committees_at_slots[0][0].shard`. * If `time_since_finality * EPOCH_LENGTH <= MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL` or `time_since_finality` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.next_seed, state.validator_registry, start_shard)` and set `state.next_seed = state.randao_mix`. Note that `start_shard` is not changed from the last epoch. @@ -1573,9 +1574,8 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com ### Finally... * 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=block.slot)`. +* 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)`. * Set `state.latest_block_hashes = state.latest_block_hashes[EPOCH_LENGTH:]`. -* Set `state.latest_state_recalculation_slot += EPOCH_LENGTH`. # Appendix ## Appendix A - Hash function From 2fc3f8879546d570a9b81da8ee21332420ac985b Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 5 Dec 2018 11:22:15 +0000 Subject: [PATCH 02/21] 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 From 20407c8dd1bfa6cadcfa48a6506b5833406c5d19 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 5 Dec 2018 14:34:50 +0000 Subject: [PATCH 03/21] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 59 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4e0925571..d7eeebb8e 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -183,7 +183,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted | `INCLUDER_REWARD_QUOTIENT` | `2**3` (= 8) | | `INACTIVITY_PENALTY_QUOTIENT` | `2**34` (= 131,072) | -* The `BASE_REWARD_QUOTIENT` constant dictates the per-epoch interest rate assuming all [validators](#dfn-validator) are participating, assuming total deposits of 1 ETH. It corresponds to ~2.57% annual interest assuming 10 million participating ETH. +* The `BASE_REWARD_QUOTIENT` constant dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch. * The `INACTIVITY_PENALTY_QUOTIENT` equals `SQRT_E_DROP_TIME**2` where `SQRT_E_DROP_TIME := 2**17 slots` (~9 days) is the amount of time it takes for the inactivity penalty to cut deposits of non-participating [validators](#dfn-validator) by ~39.4%. The portion lost by offline [validators](#dfn-validator) after `D` epochs is about `D*D/2/INACTIVITY_PENALTY_QUOTIENT`. ### Status codes @@ -400,7 +400,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted { # Slot number 'slot': 'uint64', - # Shard chain block hash + # Shard block hash 'shard_block_hash': 'hash32', } ``` @@ -1012,7 +1012,7 @@ def on_startup(initial_validator_entries: List[Any], finalized_slot=INITIAL_SLOT_NUMBER, # Recent state - latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, hash=ZERO_HASH) for _ in range(SHARD_COUNT)], + latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, shard_block_hash=ZERO_HASH) for _ in range(SHARD_COUNT)], latest_state_recalculation_slot=INITIAL_SLOT_NUMBER, latest_block_hashes=[ZERO_HASH for _ in range(EPOCH_LENGTH * 2)], latest_penalized_exit_balances=[], @@ -1350,9 +1350,8 @@ 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 `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`. +* Let `base_reward_quotient = BASE_REWARD_QUOTIENT * integer_squareroot(total_balance // GWEI_PER_ETH)`. +* Let `base_reward(v) = get_effective_balance(v) // base_reward_quotient` for any validator `v`. [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch: @@ -1365,18 +1364,19 @@ All [validators](#dfn-validator): * Let `previous_epoch_attestations = [a for a in state.latest_attestations if s - EPOCH_LENGTH <= a.slot < s]`. * Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, block, s - EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. -* Let `previous_epoch_boundary_attesters` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`. +* Let `previous_epoch_boundary_attesters` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`. * Let `previous_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in previous_epoch_boundary_attesters])`. -For every `ShardCommittee` object `obj` in `state.shard_committees_at_slots`: +For every `shard_committee` in `state.shard_committees_at_slots`: -* Let `attesting_validators(obj, shard_block_hash)` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_attestations + previous_epoch_attestations if a.shard == obj.shard and a.shard_block_hash == shard_block_hash]`. -* Let `attesting_validators(obj)` be equal to `attesting_validators(obj, shard_block_hash)` for the value of `shard_block_hash` such that `sum([get_effective_balance(v) for v in attesting_validators(obj, shard_block_hash)])` is maximized (ties broken by favoring lower `shard_block_hash` values). -* Let `total_attesting_balance(obj)` be the sum of the balances-at-stake of `attesting_validators(obj)`. -* 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 `attesting_validators(shard_committee, shard_block_hash)` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_attestations + previous_epoch_attestations if a.shard == shard_committee.shard and a.shard_block_hash == shard_block_hash]`. +* Let `attesting_validators(shard_committee)` be equal to `attesting_validators(shard_committee, shard_block_hash)` for the value of `shard_block_hash` such that `sum([get_effective_balance(v) for v in attesting_validators(shard_committee, shard_block_hash)])` is maximized (ties broken by favoring lower `shard_block_hash` values). +* Let `total_attesting_balance(shard_committee)` be the sum of the balances-at-stake of `attesting_validators(shard_committee)`. +* Let `winning_hash(shard_committee)` be the winning `shard_block_hash` value. +* Let `total_balance(shard_committee) = sum([get_effective_balance(v) for v in shard_committee.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. +* Let `inclusion_slot(v) = a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`. +* Let `inclusion_distance(v) = a.slot_included - a.data.slot` where `a` is the above attestation. * Let `adjust_for_inclusion_distance(magnitude, distance)` be the function below. ```python @@ -1390,18 +1390,17 @@ def adjust_for_inclusion_distance(magnitude: int, distance: int) -> int: ### Receipt roots -If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then: +If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: -* 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.processed_pow_receipt_root = x.receipt_root` if `x.votes * 2 > POW_RECEIPT_ROOT_VOTING_PERIOD` for some `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` (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. +* Set `state.justification_bitfield = (state.justification_bitfield * 2) % 2**64`. +* Set `state.justification_bitfield &= 2` and `state.justified_slot = s - EPOCH_LENGTH` if `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance`. +* Set `state.justification_bitfield &= 1` and `state.justified_slot = s` if `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance`. ### Finalization @@ -1413,32 +1412,32 @@ If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`, then: 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)`. +* Set `crosslinks[shard] = CrosslinkRecord(slot=state.slot, shard_block_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. -* Let `time_since_finality = state.slot - state.finalized_slot`. +* Let `slots_since_finality = state.slot - state.finalized_slot`. -Case 1: `time_since_finality <= 4 * EPOCH_LENGTH`: +Case 1: `slots_since_finality <= 4 * EPOCH_LENGTH`: * Any [validator](#dfn-validator) `v` in `previous_epoch_boundary_attesters` gains `adjust_for_inclusion_distance(base_reward(v) * previous_epoch_boundary_attesting_balance // total_balance, inclusion_distance(v))`. * Any [active validator](#dfn-active-validator) `v` not in `previous_epoch_boundary_attesters` loses `base_reward(v)`. -Case 2: `time_since_finality > 4 * EPOCH_LENGTH`: +Case 2: `slots_since_finality > 4 * EPOCH_LENGTH`: * Any [validator](#dfn-validator) in `previous_epoch_boundary_attesters` sees their balance unchanged. -* Any [active validator](#dfn-active-validator) `v` not in `previous_epoch_boundary_attesters`, and any [validator](#dfn-validator) with `status == EXITED_WITH_PENALTY`, loses `base_reward(v) + get_effective_balance(v) * time_since_finality // INACTIVITY_PENALTY_QUOTIENT`. +* Any [active validator](#dfn-active-validator) `v` not in `previous_epoch_boundary_attesters`, and any [validator](#dfn-validator) with `status == EXITED_WITH_PENALTY`, loses `base_reward(v) + get_effective_balance(v) * slots_since_finality // INACTIVITY_PENALTY_QUOTIENT`. 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`. ### Crosslink rewards and penalties -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: +For every `shard_committee` 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 shard_committee.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)`. +* If `v in attesting_validators(shard_committee)`, `v.balance += adjust_for_inclusion_distance(base_reward(v) * total_attesting_balance(shard_committee) // total_balance(shard_committee)), inclusion_distance(v))`. +* If `v not in attesting_validators(shard_committee)`, `v.balance -= base_reward(v)`. ### Validator registry @@ -1550,9 +1549,9 @@ Also perform the following updates: 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`. +* Let `slots_since_finality = state.slot - state.validator_registry_latest_change_slot`. * Let `start_shard = state.shard_committees_at_slots[0][0].shard`. -* If `time_since_finality * EPOCH_LENGTH <= MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL` or `time_since_finality` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.next_seed, state.validator_registry, start_shard)` and set `state.next_seed = state.randao_mix`. Note that `start_shard` is not changed from the last epoch. +* If `slots_since_finality * EPOCH_LENGTH <= MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL` or `slots_since_finality` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.next_seed, state.validator_registry, start_shard)` and set `state.next_seed = state.randao_mix`. Note that `start_shard` is not changed from the last epoch. ### Proposer reshuffling From eb26e10faec9357298ac6d8cf327d6c790f84688 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 5 Dec 2018 17:30:37 +0000 Subject: [PATCH 04/21] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d7eeebb8e..55060e4d4 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1185,7 +1185,16 @@ def exit_validator(index: int, ## Per-slot processing -Below are the processing 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. If there is no block from the proposer for a given slot a "skip block" is inserted into the beacon chain. The skip block is a `BeaconBlock` where the non-zero fields are: + +* `parent_hash`: the hash of the previous block (which may itself be a skip block) +* `slot`: the current slot +* `state_root`: the state root at the current slot + +```python + +``` +Denote by `block` the associated block, possibly a skip block. * 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`. From 3bd56891c5fde9b31c3493d1667e434693c46459 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 12:40:08 -0600 Subject: [PATCH 05/21] cleanup per slot processing --- specs/core/0_beacon-chain.md | 91 +++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 55060e4d4..487e83606 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -360,7 +360,6 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted # Recent state 'latest_crosslinks': [CrosslinkRecord], - 'latest_state_recalculation_slot': 'uint64', 'latest_block_hashes': ['hash32'], # Needed to process attestations, older to newer 'latest_penalized_exit_balances': ['uint64'], # Balances penalized at every withdrawal period 'latest_attestations': [PendingAttestationRecord], @@ -671,7 +670,7 @@ def lmd_ghost(store, start): while 1: children = get_children(head) if len(children) == 0: - return head + return head head = max(children, key=get_vote_count) ``` @@ -682,7 +681,7 @@ We now define the state transition function. At a high level the state transitio 1. The per-slot transitions, which happens every slot, and only affects a parts of the `state`. 2. The per-epoch transitions, which happens at every epoch boundary (i.e. `state.slot % EPOCH_LENGTH == 0`), and affects the entire `state`. -The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization. The per-slot transitions generally focuses on verifying aggregate signatures and saving temporary records relating to the per-slot activity in the `BeaconState`. +The per-slot transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-slot activity in the `BeaconState`. The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization. ### Helper functions @@ -835,7 +834,7 @@ def get_shard_committees_at_slot(state: BeaconState, """ Returns the ``ShardCommittee`` for the ``slot``. """ - earliest_slot_in_array = state.latest_state_recalculation_slot - EPOCH_LENGTH + earliest_slot_in_array = state.slot - (state.slot % EPOCH_LENGTH) - EPOCH_LENGTH assert earliest_slot_in_array <= slot < earliest_slot_in_array + EPOCH_LENGTH * 2 return state.shard_committees_at_slots[slot - earliest_slot_in_array] ``` @@ -844,22 +843,22 @@ def get_shard_committees_at_slot(state: BeaconState, ```python def get_block_hash(state: BeaconState, - current_block: BeaconBlock, slot: int) -> Hash32: """ Returns the block hash at a recent ``slot``. """ - earliest_slot_in_array = current_block.slot - len(state.latest_block_hashes) - assert earliest_slot_in_array <= slot < current_block.slot + earliest_slot_in_array = state.slot - len(state.latest_block_hashes) + assert earliest_slot_in_array <= slot < state.slot return state.latest_block_hashes[slot - earliest_slot_in_array] ``` -`get_block_hash(_, _, s)` should always return the block hash in the beacon chain at slot `s`, and `get_shard_committees_at_slot(_, s)` should not change unless the [validator](#dfn-validator) registry changes. +`get_block_hash(_, s)` should always return the block hash in the beacon chain at slot `s`, and `get_shard_committees_at_slot(_, s)` should not change unless the [validator](#dfn-validator) registry changes. #### `get_beacon_proposer_index` ```python -def get_beacon_proposer_index(state:BeaconState, slot: int) -> int: +def get_beacon_proposer_index(state:BeaconState, + slot: int) -> int: """ Returns the beacon proposer index for the ``slot``. """ @@ -1013,7 +1012,6 @@ def on_startup(initial_validator_entries: List[Any], # Recent state latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, shard_block_hash=ZERO_HASH) for _ in range(SHARD_COUNT)], - latest_state_recalculation_slot=INITIAL_SLOT_NUMBER, latest_block_hashes=[ZERO_HASH for _ in range(EPOCH_LENGTH * 2)], latest_penalized_exit_balances=[], latest_attestations=[], @@ -1105,6 +1103,7 @@ def get_new_validators(validators: List[ValidatorRecord], return validators_copy, index ``` + `BLSVerify` is a function for verifying a BLS12-381 signature, defined in the [BLS12-381 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_verify.md). Now, to add a [validator](#dfn-validator) or top up an existing [validator](#dfn-validator)'s balance: @@ -1185,46 +1184,51 @@ def exit_validator(index: int, ## Per-slot processing -Below are the processing steps that happen at every slot. If there is no block from the proposer for a given slot a "skip block" is inserted into the beacon chain. The skip block is a `BeaconBlock` where the non-zero fields are: +Below are the processing steps that happen at every slot. -* `parent_hash`: the hash of the previous block (which may itself be a skip block) -* `slot`: the current slot -* `state_root`: the state root at the current slot +First we define the following helpers. ```python - -``` -Denote by `block` the associated block, possibly a skip block. - -* 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: - -```python -def append_to_recent_block_hashes(old_block_hashes: List[Hash32], - parent_slot: int, - current_slot: int, - parent_hash: Hash32) -> List[Hash32]: - d = current_slot - parent_slot - return old_block_hashes + [parent_hash] * d -``` - -The output of `get_block_hash` should not change, except that it will no longer throw for `current_slot - 1`. Also, check that the block's `ancestor_hashes` array was correctly updated, using the following algorithm: - -```python -def update_ancestor_hashes(parent_ancestor_hashes: List[Hash32], - parent_slot: int, - parent_hash: Hash32) -> List[Hash32]: - new_ancestor_hashes = copy.copy(parent_ancestor_hashes) +def get_updated_ancestor_hashes(parent: BeaconBlock, + parent_hash: Hash32) -> List[Hash32]: + new_ancestor_hashes = copy.copy(parent.ancestor_hashes) for i in range(32): - if parent_slot % 2**i == 0: + if parent.slot % 2**i == 0: new_ancestor_hashes[i] = parent_hash return new_ancestor_hashes + + +def get_skip_block(slot: int, + parent: BeaconBlock, + parent_hash: Hash32) -> BeaconBlock: + return BeaconBlock( + slot=state.slot, + randao_reveal=ZERO_HASH, + candidate_pow_receipt_root=ZERO_HASH, + ancestor_hashes=get_updated_ancestor_hashes(parent, parent_hash), + state_root=ZERO_HASH, # filled in with post-state-transition root + attestations=[], + specials=[], + proposer_signature=[0, 0] + ) ``` +* Let `parent_hash` be the hash of the beacon block at the immediate previous slot. +* Let `parent` be the `BeaconBlock` with the hash `parent_hash`. + +If there is a block from the proposer for `state.slot`, we process that incoming block: +* Let `block` be that associated incoming block. + +If there is not a block from the proposer for `state.slot`, a "skip block" is inserted into the beacon chain: +* Let `block` be a skip block defined by `get_skip_block(state.slot, parent, parent_hash)`. + +Set `state.latest_block_hashes` to `state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`.) +Also, check that the `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. + ### Proposer signature +If `block` is not a skip block: + * 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))`. @@ -1237,7 +1241,7 @@ For each `attestation` in `block.attestations`: * Verify that `attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY`. * Verify that `attestation.data.slot >= max(parent.slot - EPOCH_LENGTH + 1, 0)`. -* Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.latest_state_recalculation_slot else state.previous_justified_slot`. +* Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.slot - (state.slot % EPOCH_LENGTH) else state.previous_justified_slot`. * Verify that `attestation.data.justified_block_hash` is equal to `get_block_hash(state, block, attestation.data.justified_slot)`. * Verify that either `attestation.data.latest_crosslink_hash` or `attestation.data.shard_block_hash` equals `state.crosslinks[shard].shard_block_hash`. * `aggregate_signature` verification: @@ -1264,7 +1268,10 @@ If `block` is not a skip block: ### 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)`. +If `block` is not a skip block: + +* 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)`. ### Special objects From c154c65f1465defd810298940614f4a009075fae Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 12:42:16 -0600 Subject: [PATCH 06/21] cleanup calls to 'get_block_hash' --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 487e83606..cce578964 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1242,7 +1242,7 @@ For each `attestation` in `block.attestations`: * Verify that `attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY`. * Verify that `attestation.data.slot >= max(parent.slot - EPOCH_LENGTH + 1, 0)`. * Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.slot - (state.slot % EPOCH_LENGTH) else state.previous_justified_slot`. -* Verify that `attestation.data.justified_block_hash` is equal to `get_block_hash(state, block, attestation.data.justified_slot)`. +* Verify that `attestation.data.justified_block_hash` is equal to `get_block_hash(state, attestation.data.justified_slot)`. * Verify that either `attestation.data.latest_crosslink_hash` or `attestation.data.shard_block_hash` equals `state.crosslinks[shard].shard_block_hash`. * `aggregate_signature` verification: * Let `participants = get_attestation_participants(state, attestation.data, attestation.participation_bitfield)`. @@ -1372,14 +1372,14 @@ All [validators](#dfn-validator): [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch: * Let `this_epoch_attestations = [a for a in state.latest_attestations if s <= a.data.slot < s + EPOCH_LENGTH]`. (Note: this is the set of attestations of slots in the epoch `s...s+EPOCH_LENGTH-1`, _not_ attestations that got included in the chain during the epoch `s...s+EPOCH_LENGTH-1`.) -* Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_hash(state, block, s) and a.justified_slot == state.justified_slot]`. +* Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_hash(state, s) and a.justified_slot == state.justified_slot]`. * Let `this_epoch_boundary_attesters` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_boundary_attestations]`. * Let `this_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in this_epoch_boundary_attesters])`. [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the previous epoch: * Let `previous_epoch_attestations = [a for a in state.latest_attestations if s - EPOCH_LENGTH <= a.slot < s]`. -* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, block, s - EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. +* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, s - EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. * Let `previous_epoch_boundary_attesters` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`. * Let `previous_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in previous_epoch_boundary_attesters])`. From cb871c3f0ee134308f5d11f6597e73d28d7bb711 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 12:47:21 -0600 Subject: [PATCH 07/21] minor cleanups --- specs/core/0_beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index cce578964..e7eee3809 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1222,8 +1222,9 @@ If there is a block from the proposer for `state.slot`, we process that incoming If there is not a block from the proposer for `state.slot`, a "skip block" is inserted into the beacon chain: * Let `block` be a skip block defined by `get_skip_block(state.slot, parent, parent_hash)`. -Set `state.latest_block_hashes` to `state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`.) -Also, check that the `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. +Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. + +Set `state.latest_block_hashes = state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). ### Proposer signature From 3a677eefa956e5cb5a443ee951acaf1a22e40915 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 12:49:45 -0600 Subject: [PATCH 08/21] minor cleanup --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e7eee3809..e21784656 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -857,7 +857,7 @@ def get_block_hash(state: BeaconState, #### `get_beacon_proposer_index` ```python -def get_beacon_proposer_index(state:BeaconState, +def get_beacon_proposer_index(state: BeaconState, slot: int) -> int: """ Returns the beacon proposer index for the ``slot``. From 59494d17745c6bf7153766ff33299818f0bcb82a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 13:03:24 -0600 Subject: [PATCH 09/21] add state root processing --- specs/core/0_beacon-chain.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e21784656..5e51ad4ab 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -90,6 +90,7 @@ - [Validator registry](#validator-registry) - [Proposer reshuffling](#proposer-reshuffling) - [Final updates](#final-updates) + - [State root processing](#state-root-processing) - [Appendix](#appendix) - [Appendix A - Hash function](#appendix-a---hash-function) - [References](#references) @@ -1603,6 +1604,14 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com * 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:]`. +## State root processing + +If `block` is not a skip block: +* Verify `block.state_root == hash(state)` + +If `block` is a skip block: +* Set `block.state_root = hash(state)` + # Appendix ## Appendix A - Hash function From e894a2dc1861deda97bd349f033f994cd9611f92 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 13:55:56 -0600 Subject: [PATCH 10/21] fix issues in justificaiton/finality --- specs/core/0_beacon-chain.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e77774b76..bdd9e4495 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1422,14 +1422,14 @@ If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: * Set `state.previous_justified_slot = state.justified_slot`. * Set `state.justification_bitfield = (state.justification_bitfield * 2) % 2**64`. -* Set `state.justification_bitfield &= 2` and `state.justified_slot = s - EPOCH_LENGTH` if `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance`. -* Set `state.justification_bitfield &= 1` and `state.justified_slot = s` if `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance`. +* Set `state.justification_bitfield |= 2` and `state.justified_slot = s - EPOCH_LENGTH` if `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance`. +* Set `state.justification_bitfield |= 1` and `state.justified_slot = s` if `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance`. -### Finalization +# Finalization -* 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)`. +* Set `state.finalized_slot = state.justified_slot - EPOCH_LENGTH` if `state.justified_slot == s and state.justification_bitfield % 4 == 3`. +* Set `state.finalized_slot = state.justified_slot - EPOCH_LENGTH` if `state.justified_slot == s - EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. +* Set `state.finalized_slot = state.justified_slot - EPOCH_LENGTH` if `state.justified_slot == s - 2 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. ### Crosslinks From 1ccb1074c5599419fd41697ce7fd4daa224ca035 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 14:07:41 -0600 Subject: [PATCH 11/21] fix just/finality --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index bdd9e4495..0d91eb1d2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1425,11 +1425,11 @@ If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: * Set `state.justification_bitfield |= 2` and `state.justified_slot = s - EPOCH_LENGTH` if `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance`. * Set `state.justification_bitfield |= 1` and `state.justified_slot = s` if `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance`. -# Finalization +### Finalization -* Set `state.finalized_slot = state.justified_slot - EPOCH_LENGTH` if `state.justified_slot == s and state.justification_bitfield % 4 == 3`. -* Set `state.finalized_slot = state.justified_slot - EPOCH_LENGTH` if `state.justified_slot == s - EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. -* Set `state.finalized_slot = state.justified_slot - EPOCH_LENGTH` if `state.justified_slot == s - 2 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. +* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == s - 1 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3`. +* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == s - 2 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. +* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == s - 3 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. ### Crosslinks From 22a77c331bd5bf3ecc86f81fbbc338aa73e5b8a9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 14:35:58 -0600 Subject: [PATCH 12/21] fix change_validators --- specs/core/0_beacon-chain.md | 46 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 0d91eb1d2..66d0d2f52 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1469,27 +1469,36 @@ If the following are satisfied: * `state.finalized_slot > state.validator_registry_latest_change_slot` * `state.latest_crosslinks[shard].slot > state.validator_registry_latest_change_slot` for every shard number `shard` in `state.shard_committees_at_slots` -update the validator registry by running +update the validator registry and associated fields by running ```python - update_validator_registry( - copy.deepcopy(state.validator_registry), - copy.deepcopy(state.latest_penalized_exit_balances), - state.validator_registry_delta_chain_tip, - state.slot - ) +def change_validators(state: BeaconState) -> None: + """ + Change validator registry. + Note that this function mutates ``state``. + """ + state.validator_registry, state.latest_penalized_exit_balances, state.validator_registry_delta_chain_tip = get_update_validator_registry( + state.validator_registry, + state.latest_penalized_exit_balances, + state.validator_registry_delta_chain_tip, + state.slot + ) ``` -where +which utilizes the following helper ```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]: +def get_updated_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]: """ - Update the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``. + return the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``. """ + # make copies to prevent mutating inputs + validator_registry = copy.deepcopy(state.validator_registry) + latest_penalized_exit_balances = copy.deepcopy(latest_penalized_exit_balances) + # The active validators active_validator_indices = get_active_validator_indices(validator_registry) # The total effective balance of active validators @@ -1503,8 +1512,7 @@ def update_validator_registry(validator_registry: List[ValidatorRecord], # Activate validators within the allowable balance churn balance_churn = 0 - for i in range(len(validator_registry)): - validator = validator_registry[i] + for i, validator in enumerate(validator_registry): if validator.status == PENDING_ACTIVATION and validator.balance >= MAX_DEPOSIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(validator) @@ -1523,8 +1531,7 @@ def update_validator_registry(validator_registry: List[ValidatorRecord], # Exit validators within the allowable balance churn balance_churn = 0 - for i in range(len(validators)): - validator = validator_registry[i] + for i, validator in enumerate(validators): if validator.status == ACTIVE_PENDING_EXIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(validator) @@ -1556,10 +1563,7 @@ def update_validator_registry(validator_registry: List[ValidatorRecord], for v in validators_to_penalize: v.balance -= get_effective_balance(v) * min(total_penalties * 3, total_balance) // total_balance - # 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 + return validator_registry, latest_penalized_exit_balances, validator_registry_delta_chain_tip ``` Also perform the following updates: From db65429cc8c85c599c80ff4e7a719c4892008e62 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 5 Dec 2018 19:24:37 -0600 Subject: [PATCH 13/21] rework to do virtual block transitions --- specs/core/0_beacon-chain.md | 51 ++++++++++++++---------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 66d0d2f52..4d17e2eb3 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -637,7 +637,7 @@ Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Cli For a beacon chain block, `block`, to be processed by a node, the following conditions must be met: -* The parent block (possibly a skip block) with hash `block.ancestor_hashes[0]` has been processed and accepted. +* The parent block with hash `block.ancestor_hashes[0]` has been processed and accepted. * The Ethereum 1.0 block pointed to by the `state.processed_pow_receipt_root` has been processed and accepted. * The node's local clock time is greater than or equal to `state.genesis_time + block.slot * SLOT_DURATION`. @@ -1192,7 +1192,7 @@ def exit_validator(index: int, Below are the processing steps that happen at every slot. -First we define the following helpers. +First we define the following helper. ```python def get_updated_ancestor_hashes(parent: BeaconBlock, @@ -1202,39 +1202,23 @@ def get_updated_ancestor_hashes(parent: BeaconBlock, if parent.slot % 2**i == 0: new_ancestor_hashes[i] = parent_hash return new_ancestor_hashes - - -def get_skip_block(slot: int, - parent: BeaconBlock, - parent_hash: Hash32) -> BeaconBlock: - return BeaconBlock( - slot=state.slot, - randao_reveal=ZERO_HASH, - candidate_pow_receipt_root=ZERO_HASH, - ancestor_hashes=get_updated_ancestor_hashes(parent, parent_hash), - state_root=ZERO_HASH, # filled in with post-state-transition root - attestations=[], - specials=[], - proposer_signature=[0, 0] - ) ``` -* Let `parent_hash` be the hash of the beacon block at the immediate previous slot. +* Let `parent_hash` be the hash of the latest beacon block that was processed in the chain. * Let `parent` be the `BeaconBlock` with the hash `parent_hash`. +* Set `state.latest_block_hashes = state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). If there is a block from the proposer for `state.slot`, we process that incoming block: +* Let `skip_block = False` * Let `block` be that associated incoming block. +* Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. -If there is not a block from the proposer for `state.slot`, a "skip block" is inserted into the beacon chain: -* Let `block` be a skip block defined by `get_skip_block(state.slot, parent, parent_hash)`. - -Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. - -Set `state.latest_block_hashes = state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). +If there is not a block from the proposer for `state.slot`, the slot is processed without a block: +* Let `skip_block = True` ### Proposer signature -If `block` is not a skip block: +If not `skip_block`: * 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))`. @@ -1242,6 +1226,8 @@ If `block` is not a skip block: ### Attestations +*If `skip_block`, do not process attestations. Move to next section.* + * Verify that `len(block.attestations) <= MAX_ATTESTATIONS_PER_BLOCK`. For each `attestation` in `block.attestations`: @@ -1260,11 +1246,11 @@ For each `attestation` in `block.attestations`: ### RANDAO -If `block` is a skip block: +If `skip_block`: * Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. -If `block` is not a skip block: +If not `skip_block`: * Let `repeat_hash(x, n) = x if n == 0 else repeat_hash(hash(x), n-1)`. * Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`. @@ -1275,13 +1261,16 @@ If `block` is not a skip block: ### PoW receipt root -If `block` is not a skip block: +*If `skip_block`, do not process PoW receipt root. Move to next section.* + * 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)`. ### Special objects +*If `skip_block`, do not process special objects. Move to next section.* + * 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)`. @@ -1615,11 +1604,9 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com ## State root processing -If `block` is not a skip block: -* Verify `block.state_root == hash(state)` +*If `skip_block`, do not process state root. Move to next section.* -If `block` is a skip block: -* Set `block.state_root = hash(state)` +Verify `block.state_root == hash(state)` # Appendix ## Appendix A - Hash function From 464ad63464afeeb980b993226d76c8e882247016 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 10:25:35 -0600 Subject: [PATCH 14/21] minor slot cleanups --- specs/core/0_beacon-chain.md | 53 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 8026523ca..c286891fb 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -638,6 +638,7 @@ Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Cli For a beacon chain block, `block`, to be processed by a node, the following conditions must be met: * The parent block with hash `block.ancestor_hashes[0]` has been processed and accepted. +* The node has processed its `state` up to slot, `block.slot - 1`. * The Ethereum 1.0 block pointed to by the `state.processed_pow_receipt_root` has been processed and accepted. * The node's local clock time is greater than or equal to `state.genesis_time + block.slot * SLOT_DURATION`. @@ -868,6 +869,18 @@ def get_beacon_proposer_index(state: BeaconState, return first_committee[slot % len(first_committee)] ``` +#### `get_updated_ancestor_hashes` + +```python +def get_updated_ancestor_hashes(parent: BeaconBlock, + parent_hash: Hash32) -> List[Hash32]: + new_ancestor_hashes = copy.deepcopy(parent.ancestor_hashes) + for i in range(32): + if parent.slot % 2**i == 0: + new_ancestor_hashes[i] = parent_hash + return new_ancestor_hashes +``` + #### `get_attestation_participants` ```python @@ -1192,33 +1205,22 @@ def exit_validator(index: int, Below are the processing steps that happen at every slot. -First we define the following helper. - -```python -def get_updated_ancestor_hashes(parent: BeaconBlock, - parent_hash: Hash32) -> List[Hash32]: - new_ancestor_hashes = copy.copy(parent.ancestor_hashes) - for i in range(32): - if parent.slot % 2**i == 0: - new_ancestor_hashes[i] = parent_hash - return new_ancestor_hashes -``` - * Let `parent_hash` be the hash of the latest beacon block that was processed in the chain. * Let `parent` be the `BeaconBlock` with the hash `parent_hash`. +* Set `state.slot += 1` * Set `state.latest_block_hashes = state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). If there is a block from the proposer for `state.slot`, we process that incoming block: -* Let `skip_block = False` +* Let `slot_has_block = True` * Let `block` be that associated incoming block. * Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. If there is not a block from the proposer for `state.slot`, the slot is processed without a block: -* Let `skip_block = True` +* Let `slot_has_block = False` ### Proposer signature -If not `skip_block`: +If `slot_has_block`: * 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))`. @@ -1226,14 +1228,14 @@ If not `skip_block`: ### Attestations -*If `skip_block`, do not process attestations. Move to next section.* +*If not `slot_has_block`, do not process attestations. Move to next section.* * Verify that `len(block.attestations) <= MAX_ATTESTATIONS_PER_BLOCK`. For each `attestation` in `block.attestations`: * Verify that `attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY`. -* Verify that `attestation.data.slot >= max(parent.slot - EPOCH_LENGTH + 1, 0)`. +* Verify that `attestation.data.slot >= max(state.slot - EPOCH_LENGTH, 0)`. * Verify that `attestation.data.justified_slot` is equal to `state.justified_slot if attestation.data.slot >= state.slot - (state.slot % EPOCH_LENGTH) else state.previous_justified_slot`. * Verify that `attestation.data.justified_block_hash` is equal to `get_block_hash(state, attestation.data.justified_slot)`. * Verify that either `attestation.data.latest_crosslink_hash` or `attestation.data.shard_block_hash` equals `state.latest_crosslinks[shard].shard_block_hash`. @@ -1246,11 +1248,7 @@ For each `attestation` in `block.attestations`: ### RANDAO -If `skip_block`: - -* Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. - -If not `skip_block`: +If `slot_has_block`: * Let `repeat_hash(x, n) = x if n == 0 else repeat_hash(hash(x), n-1)`. * Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`. @@ -1259,9 +1257,14 @@ If not `skip_block`: * Set `proposer.randao_commitment = block.randao_reveal`. * Set `proposer.randao_skips = 0`. + +If not `slot_has_block`: + +* Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. + ### PoW receipt root -*If `skip_block`, do not process PoW receipt root. Move to next section.* +*If not `slot_has_block`, do not process PoW receipt root. Move to next section.* * 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`. @@ -1269,7 +1272,7 @@ If not `skip_block`: ### Special objects -*If `skip_block`, do not process special objects. Move to next section.* +*If not `slot_has_block`, do not process special objects. Move to next section.* * 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)`. @@ -1604,7 +1607,7 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com ## State root processing -*If `skip_block`, do not process state root. Move to next section.* +*If not `slot_has_block`, do not process state root. Move to next section.* Verify `block.state_root == hash(state)` From 5e058a15b890bf3690accadb859fa407709a03ef Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 10:41:50 -0600 Subject: [PATCH 15/21] remove 'slot_hash_block' --- specs/core/0_beacon-chain.md | 44 ++++++++++++------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c286891fb..1957f1be6 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -872,12 +872,12 @@ def get_beacon_proposer_index(state: BeaconState, #### `get_updated_ancestor_hashes` ```python -def get_updated_ancestor_hashes(parent: BeaconBlock, - parent_hash: Hash32) -> List[Hash32]: - new_ancestor_hashes = copy.deepcopy(parent.ancestor_hashes) +def get_updated_ancestor_hashes(latest_block: BeaconBlock, + latest_hash: Hash32) -> List[Hash32]: + new_ancestor_hashes = copy.deepcopy(latest_block.ancestor_hashes) for i in range(32): - if parent.slot % 2**i == 0: - new_ancestor_hashes[i] = parent_hash + if latest_block.slot % 2**i == 0: + new_ancestor_hashes[i] = latest_hash return new_ancestor_hashes ``` @@ -1205,31 +1205,28 @@ def exit_validator(index: int, Below are the processing steps that happen at every slot. -* Let `parent_hash` be the hash of the latest beacon block that was processed in the chain. -* Let `parent` be the `BeaconBlock` with the hash `parent_hash`. +* Let `latest_hash` be the hash of the latest beacon block that was processed in the chain. +* Let `latest_block` be the `BeaconBlock` with the hash `latest_hash`. * Set `state.slot += 1` -* Set `state.latest_block_hashes = state.latest_block_hashes + [parent_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). +* Set `state.latest_block_hashes = state.latest_block_hashes + [latest_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). If there is a block from the proposer for `state.slot`, we process that incoming block: -* Let `slot_has_block = True` * Let `block` be that associated incoming block. -* Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(parent, parent_hash)`. +* Verify that `block.slot == state.slot` +* Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(latest_block, latest_hash)`. If there is not a block from the proposer for `state.slot`, the slot is processed without a block: -* Let `slot_has_block = False` +* Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. +* Skip all other per-slot processing. Move directly to [per-epoch processing](#per-epoch-processing). ### Proposer signature -If `slot_has_block`: - * 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 -*If not `slot_has_block`, do not process attestations. Move to next section.* - * Verify that `len(block.attestations) <= MAX_ATTESTATIONS_PER_BLOCK`. For each `attestation` in `block.attestations`: @@ -1248,8 +1245,6 @@ For each `attestation` in `block.attestations`: ### RANDAO -If `slot_has_block`: - * Let `repeat_hash(x, n) = x if n == 0 else repeat_hash(hash(x), n-1)`. * Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`. * Verify that `repeat_hash(block.randao_reveal, proposer.randao_skips + 1) == proposer.randao_commitment`. @@ -1257,23 +1252,13 @@ If `slot_has_block`: * Set `proposer.randao_commitment = block.randao_reveal`. * Set `proposer.randao_skips = 0`. - -If not `slot_has_block`: - -* Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. - ### PoW receipt root -*If not `slot_has_block`, do not process PoW receipt root. Move to next section.* - - * 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)`. ### Special objects -*If not `slot_has_block`, do not process special objects. Move to next section.* - * 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)`. @@ -1607,9 +1592,8 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com ## State root processing -*If not `slot_has_block`, do not process state root. Move to next section.* - -Verify `block.state_root == hash(state)` +If there exists a `block` for the slot being processed: +* Verify `block.state_root == hash(state)` # Appendix ## Appendix A - Hash function From 330b2449bfa0821b21307a51e4e73c54fa31f365 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 15:54:39 -0600 Subject: [PATCH 16/21] pr feedback --- specs/core/0_beacon-chain.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1957f1be6..10ba031ce 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1454,7 +1454,7 @@ def change_validators(state: BeaconState) -> None: Change validator registry. Note that this function mutates ``state``. """ - state.validator_registry, state.latest_penalized_exit_balances, state.validator_registry_delta_chain_tip = get_update_validator_registry( + state.validator_registry, state.latest_penalized_exit_balances, state.validator_registry_delta_chain_tip = get_updated_validator_registry( state.validator_registry, state.latest_penalized_exit_balances, state.validator_registry_delta_chain_tip, @@ -1547,7 +1547,7 @@ 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.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 update does _not_ happen do the following: @@ -1592,8 +1592,7 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com ## State root processing -If there exists a `block` for the slot being processed: -* Verify `block.state_root == hash(state)` +Verify `block.state_root == hash(state)` if there exists a `block` for the slot being processed. # Appendix ## Appendix A - Hash function From eb7715aa41d6084257ad1cbb12257950369412d1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 16:12:33 -0600 Subject: [PATCH 17/21] pr feedback --- specs/core/0_beacon-chain.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 10ba031ce..29eea221e 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1205,8 +1205,8 @@ def exit_validator(index: int, Below are the processing steps that happen at every slot. -* Let `latest_hash` be the hash of the latest beacon block that was processed in the chain. -* Let `latest_block` be the `BeaconBlock` with the hash `latest_hash`. +* Let `latest_block` be the latest `BeaconBlock` that was processed in the chain. +* Let `latest_hash` be the hash of `latest_block`. * Set `state.slot += 1` * Set `state.latest_block_hashes = state.latest_block_hashes + [latest_hash]`. (The output of `get_block_hash` should not change, except that it will no longer throw for `state.slot - 1`). @@ -1215,8 +1215,8 @@ If there is a block from the proposer for `state.slot`, we process that incoming * Verify that `block.slot == state.slot` * Verify that `block.ancestor_hashes` equals `get_updated_ancestor_hashes(latest_block, latest_hash)`. -If there is not a block from the proposer for `state.slot`, the slot is processed without a block: -* Let `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. +If there is no block from the proposer at state.slot: +* Set `state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1`. * Skip all other per-slot processing. Move directly to [per-epoch processing](#per-epoch-processing). ### Proposer signature @@ -1449,9 +1449,9 @@ If the following are satisfied: update the validator registry and associated fields by running ```python -def change_validators(state: BeaconState) -> None: +def update_validator_registry(state: BeaconState) -> None: """ - Change validator registry. + Update validator registry. Note that this function mutates ``state``. """ state.validator_registry, state.latest_penalized_exit_balances, state.validator_registry_delta_chain_tip = get_updated_validator_registry( From ae1b64ccffbc60344c77d2f8a6ff08bcfe2da18a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 16:38:40 -0600 Subject: [PATCH 18/21] remove s helper --- specs/core/0_beacon-chain.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 29eea221e..99c0e2e45 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1342,7 +1342,7 @@ process_deposit( ## Per-epoch processing -The steps below happen when `state.slot % EPOCH_LENGTH == 0`. For simplicity we denote `state.slot - EPOCH_LENGTH` by `s`. +The steps below happen when `state.slot % EPOCH_LENGTH == 0`. ### Helpers @@ -1355,15 +1355,15 @@ All [validators](#dfn-validator): [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the current epoch: -* Let `this_epoch_attestations = [a for a in state.latest_attestations if s <= a.data.slot < s + EPOCH_LENGTH]`. (Note: this is the set of attestations of slots in the epoch `s...s+EPOCH_LENGTH-1`, _not_ attestations that got included in the chain during the epoch `s...s+EPOCH_LENGTH-1`.) -* Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_hash(state, s) and a.justified_slot == state.justified_slot]`. +* Let `this_epoch_attestations = [a for a in state.latest_attestations if state.slot - EPOCH_LENGTH <= a.data.slot < state.slot]`. (Note: this is the set of attestations of slots in the epoch `state.slot-EPOCH_LENGTH...state.slot-1`, _not_ attestations that got included in the chain during the epoch `state.slot-EPOCH_LENGTH...state.slot-1`.) +* Let `this_epoch_boundary_attestations = [a for a in this_epoch_attestations if a.data.epoch_boundary_hash == get_block_hash(state, state.slot-EPOCH_LENGTH) and a.justified_slot == state.justified_slot]`. * Let `this_epoch_boundary_attesters` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in this_epoch_boundary_attestations]`. * Let `this_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in this_epoch_boundary_attesters])`. [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the previous epoch: -* Let `previous_epoch_attestations = [a for a in state.latest_attestations if s - EPOCH_LENGTH <= a.slot < s]`. -* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, s - EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. +* Let `previous_epoch_attestations = [a for a in state.latest_attestations if state.slot - 2*EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]`. +* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, state.slot - 2*EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. * Let `previous_epoch_boundary_attesters` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`. * Let `previous_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in previous_epoch_boundary_attesters])`. @@ -1374,7 +1374,7 @@ For every `shard_committee` in `state.shard_committees_at_slots`: * Let `total_attesting_balance(shard_committee)` be the sum of the balances-at-stake of `attesting_validators(shard_committee)`. * Let `winning_hash(shard_committee)` be the winning `shard_block_hash` value. * Let `total_balance(shard_committee) = sum([get_effective_balance(v) for v in shard_committee.committee])`. - + * Let `inclusion_slot(v) = a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`. * Let `inclusion_distance(v) = a.slot_included - a.data.slot` where `a` is the above attestation. * Let `adjust_for_inclusion_distance(magnitude, distance)` be the function below. @@ -1399,14 +1399,14 @@ If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: * Set `state.previous_justified_slot = state.justified_slot`. * Set `state.justification_bitfield = (state.justification_bitfield * 2) % 2**64`. -* Set `state.justification_bitfield |= 2` and `state.justified_slot = s - EPOCH_LENGTH` if `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance`. -* Set `state.justification_bitfield |= 1` and `state.justified_slot = s` if `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance`. +* Set `state.justification_bitfield |= 2` and `state.justified_slot = state.slot - 2 * EPOCH_LENGTH` if `3 * previous_epoch_boundary_attesting_balance >= 2 * total_balance`. +* Set `state.justification_bitfield |= 1` and `state.justified_slot = state.slot - 1 * EPOCH_LENGTH` if `3 * this_epoch_boundary_attesting_balance >= 2 * total_balance`. ### Finalization -* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == s - 1 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3`. -* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == s - 2 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. -* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == s - 3 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. +* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == state.slot - 2 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3`. +* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == state.slot - 3 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. +* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == state.slot - 4 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. ### Crosslinks @@ -1586,7 +1586,7 @@ while len(state.persistent_committee_reassignments) > 0 and state.persistent_com ### Final updates -* Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < s`. +* Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < state.slot - EPOCH_LENGTH`. * 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:]`. From 98430c7c6810fefd3830c56a2c58221088c20446 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 16:47:22 -0600 Subject: [PATCH 19/21] address comments --- specs/core/0_beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 99c0e2e45..f09eecde2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1374,7 +1374,6 @@ For every `shard_committee` in `state.shard_committees_at_slots`: * Let `total_attesting_balance(shard_committee)` be the sum of the balances-at-stake of `attesting_validators(shard_committee)`. * Let `winning_hash(shard_committee)` be the winning `shard_block_hash` value. * Let `total_balance(shard_committee) = sum([get_effective_balance(v) for v in shard_committee.committee])`. - * Let `inclusion_slot(v) = a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`. * Let `inclusion_distance(v) = a.slot_included - a.data.slot` where `a` is the above attestation. * Let `adjust_for_inclusion_distance(magnitude, distance)` be the function below. @@ -1470,7 +1469,7 @@ def get_updated_validator_registry(validator_registry: List[ValidatorRecord], validator_registry_delta_chain_tip: int, current_slot: int) -> Tuple[List[ValidatorRecord], List[int], int]: """ - return the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``. + Returns the validator registry, as well as ``latest_penalized_exit_balances`` and ``validator_registry_delta_chain_tip``. """ # make copies to prevent mutating inputs validator_registry = copy.deepcopy(state.validator_registry) From 332511b42b1a6271e43fce94ab3d16ba387c6743 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 16:51:29 -0600 Subject: [PATCH 20/21] simplify finality conditions --- specs/core/0_beacon-chain.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f09eecde2..1d602dc61 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1403,9 +1403,10 @@ If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: ### Finalization -* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == state.slot - 2 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3`. -* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == state.slot - 3 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7`. -* Set `state.finalized_slot = state.previous_justified_slot` if `state.previous_justified_slot == state.slot - 4 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)`. +Set `state.finalized_slot = state.previous_justified_slot` if any of the following are true: +* `state.previous_justified_slot == state.slot - 2 * EPOCH_LENGTH and state.justification_bitfield % 4 == 3` +* `state.previous_justified_slot == state.slot - 3 * EPOCH_LENGTH and state.justification_bitfield % 8 == 7` +* `state.previous_justified_slot == state.slot - 4 * EPOCH_LENGTH and state.justification_bitfield % 16 in (15, 14)` ### Crosslinks From 15c11a46bd30677c565d4cf708b8552d9396f70f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 6 Dec 2018 16:56:23 -0600 Subject: [PATCH 21/21] consistency in multiplication of EPOCH_LENGTH formatting --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1d602dc61..83a41245a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1362,8 +1362,8 @@ All [validators](#dfn-validator): [Validators](#dfn-Validator) justifying the epoch boundary block at the start of the previous epoch: -* Let `previous_epoch_attestations = [a for a in state.latest_attestations if state.slot - 2*EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]`. -* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, state.slot - 2*EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. +* Let `previous_epoch_attestations = [a for a in state.latest_attestations if state.slot - 2 * EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]`. +* Let `previous_epoch_boundary_attestations = [a for a in this_epoch_attestations + previous_epoch_attestations if a.epoch_boundary_hash == get_block_hash(state, state.slot - 2 * EPOCH_LENGTH) and a.justified_slot == state.previous_justified_slot]`. * Let `previous_epoch_boundary_attesters` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_boundary_attestations]`. * Let `previous_epoch_boundary_attesting_balance = sum([get_effective_balance(v) for v in previous_epoch_boundary_attesters])`.