diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 36a65b243..6fe947ba8 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -212,11 +212,11 @@ The following values are (non-configurable) constants used throughout the specif | `SLOTS_PER_EPOCH` | `2**5` (= 32) | slots | 6.4 minutes | | `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes | | `MAX_SEED_LOOKAHEAD` | `2**2` (= 4) | epochs | 25.6 minutes | +| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes | | `SLOTS_PER_ETH1_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~3.4 hours | | `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | -| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes | ### State list lengths @@ -225,7 +225,7 @@ The following values are (non-configurable) constants used throughout the specif | `EPOCHS_PER_HISTORICAL_VECTOR` | `2**16` (= 65,536) | epochs | ~0.8 years | | `EPOCHS_PER_SLASHINGS_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days | | `HISTORICAL_ROOTS_LIMIT` | `2**24` (= 16,777,216) | historical roots | ~26,131 years | -| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validator spots | +| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validators | ### Rewards and penalties @@ -366,7 +366,7 @@ class DepositData(Container): pubkey: BLSPubkey withdrawal_credentials: Bytes32 amount: Gwei - signature: BLSSignature # signing over DepositMessage + signature: BLSSignature # Signing over DepositMessage ``` #### `BeaconBlockHeader` @@ -419,7 +419,7 @@ class Attestation(Container): ```python class Deposit(Container): - proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit data list root + proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root data: DepositData ``` @@ -496,8 +496,6 @@ class BeaconState(Container): ### Signed envelopes -Some messages in the protocol are wrapped in an envelope to better facilitate adding/pruning the signature and to `hash_tree_root` the `message` separate from the signature. - #### `SignedVoluntaryExit` ```python @@ -1038,7 +1036,7 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: # Compute exit queue epoch exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] - exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) + exit_queue_epoch = max(exit_epochs, default=compute_activation_exit_epoch(get_current_epoch(state))) exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) if exit_queue_churn >= get_validator_churn_limit(state): exit_queue_epoch += Epoch(1) @@ -1139,19 +1137,21 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. ## Beacon chain state transition function -The post-state corresponding to a pre-state `state` and a block `block` is defined as `state_transition(state, block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. +The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. ```python def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> BeaconState: + block = signed_block.message # Process slots (including those with no blocks) since block - process_slots(state, signed_block.message.slot) + process_slots(state, block.slot) # Verify signature if validate_result: assert verify_block_signature(state, signed_block) # Process block - process_block(state, signed_block.message) + process_block(state, block) + # Verify state root if validate_result: - assert signed_block.message.state_root == hash_tree_root(state) # Validate state root + assert block.state_root == hash_tree_root(state) # Return post-state return state ``` @@ -1327,9 +1327,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence proposer_reward = Gwei(get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT) rewards[attestation.proposer_index] += proposer_reward max_attester_reward = get_base_reward(state, index) - proposer_reward - rewards[index] += Gwei( - max_attester_reward // attestation.inclusion_delay - ) + rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay) # Inactivity penalty finality_delay = previous_epoch - state.finalized_checkpoint.epoch @@ -1338,9 +1336,8 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence for index in eligible_validator_indices: penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)) if index not in matching_target_attesting_indices: - penalties[index] += Gwei( - state.validators[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT - ) + effective_balance = state.validators[index].effective_balance + penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT) return rewards, penalties ``` @@ -1440,13 +1437,14 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: assert block.slot == state.slot # Verify that the parent matches assert block.parent_root == hash_tree_root(state.latest_block_header) - # Save current block as the new latest block + # Cache current block as the new latest block state.latest_block_header = BeaconBlockHeader( slot=block.slot, parent_root=block.parent_root, - # `state_root` is zeroed and overwritten in the next `process_slot` call + state_root=Bytes32(), # Overwritten in the next process_slot call body_root=hash_tree_root(block.body), ) + # Verify proposer is not slashed proposer = state.validators[get_beacon_proposer_index(state)] assert not proposer.slashed @@ -1498,14 +1496,14 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: - proposer = state.validators[proposer_slashing.proposer_index] - # Verify slots match + # Verify header slots match assert proposer_slashing.signed_header_1.message.slot == proposer_slashing.signed_header_2.message.slot - # But the headers are different - assert proposer_slashing.signed_header_1.message != proposer_slashing.signed_header_2.message - # Check proposer is slashable + # Verify the headers are different + assert proposer_slashing.signed_header_1 != proposer_slashing.signed_header_2 + # Verify the proposer is slashable + proposer = state.validators[proposer_slashing.proposer_index] assert is_slashable_validator(proposer, get_current_epoch(state)) - # Signatures are valid + # Verify signatures for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) signing_root = compute_signing_root(signed_header.message, domain) @@ -1560,7 +1558,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: assert data.source == state.previous_justified_checkpoint state.previous_epoch_attestations.append(pending_attestation) - # Check signature + # Verify signature assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) ``` @@ -1572,7 +1570,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: assert is_valid_merkle_branch( leaf=hash_tree_root(deposit.data), branch=deposit.proof, - depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the `List` length mix-in + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in index=state.eth1_deposit_index, root=state.eth1_data.deposit_root, ) @@ -1584,14 +1582,14 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: amount = deposit.data.amount validator_pubkeys = [v.pubkey for v in state.validators] if pubkey not in validator_pubkeys: - # Verify the deposit signature (proof of possession) for new validators. - # Note: The deposit contract does not check signatures. - # Note: Deposits are valid across forks, thus the deposit domain is retrieved directly from `compute_domain`. + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract deposit_message = DepositMessage( pubkey=deposit.data.pubkey, withdrawal_credentials=deposit.data.withdrawal_credentials, - amount=deposit.data.amount) - signing_root = compute_signing_root(deposit_message, compute_domain(DOMAIN_DEPOSIT)) + amount=deposit.data.amount, + ) + domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks + signing_root = compute_signing_root(deposit_message, domain) if not bls.Verify(pubkey, signing_root, deposit.data.signature): return @@ -1620,7 +1618,7 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu validator = state.validators[voluntary_exit.validator_index] # Verify the validator is active assert is_active_validator(validator, get_current_epoch(state)) - # Verify the validator has not yet exited + # Verify exit has not been initiated assert validator.exit_epoch == FAR_FUTURE_EPOCH # Exits must specify an epoch when they become valid; they are not valid before then assert get_current_epoch(state) >= voluntary_exit.epoch