diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 30f9047c6..b58f819b5 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -16,7 +16,7 @@ - [Initial values](#initial-values) - [Time parameters](#time-parameters) - [Reward and penalty quotients](#reward-and-penalty-quotients) - - [Status codes](#status-codes) + - [Status flags](#status-flags) - [Max operations per block](#max-operations-per-block) - [Validator registry delta flags](#validator-registry-delta-flags) - [Signature domains](#signature-domains) @@ -46,14 +46,14 @@ - [`ValidatorRecord`](#validatorrecord) - [`CrosslinkRecord`](#crosslinkrecord) - [`ShardCommittee`](#shardcommittee) - - [`ShardReassignmentRecord`](#shardreassignmentrecord) - - [`CandidatePoWReceiptRootRecord`](#candidatepowreceiptrootrecord) + - [`DepositRootVote`](#depositrootvote) - [`PendingAttestationRecord`](#pendingattestationrecord) - [`ForkData`](#forkdata) - [`ValidatorRegistryDeltaBlock`](#validatorregistrydeltablock) - [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) - [Deposit arguments](#deposit-arguments) - - [`Eth1Deposit` logs](#eth1deposit-logs) + - [Withdrawal credentials](#withdrawal-credentials) + - [`Deposit` logs](#deposit-logs) - [`ChainStart` log](#chainstart-log) - [Vyper code](#vyper-code) - [Beacon chain processing](#beacon-chain-processing) @@ -66,7 +66,7 @@ - [`get_active_validator_indices`](#get_active_validator_indices) - [`shuffle`](#shuffle) - [`split`](#split) - - [`get_new_shuffling`](#get_new_shuffling) + - [`get_shuffling`](#get_shuffling) - [`get_shard_committees_at_slot`](#get_shard_committees_at_slot) - [`get_block_root`](#get_block_root) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) @@ -74,7 +74,6 @@ - [`get_attestation_participants`](#get_attestation_participants) - [`bytes1`, `bytes2`, ...](#bytes1-bytes2-) - [`get_effective_balance`](#get_effective_balance) - - [`get_new_validator_registry_delta_chain_tip`](#get_new_validator_registry_delta_chain_tip) - [`get_fork_version`](#get_fork_version) - [`get_domain`](#get_domain) - [`verify_slashable_vote_data`](#verify_slashable_vote_data) @@ -86,7 +85,7 @@ - [`bls_aggregate_pubkeys`](#bls_aggregate_pubkeys) - [On startup](#on-startup) - [Routine for processing deposits](#routine-for-processing-deposits) - - [Routine for updating validator status](#routine-for-updating-validator-status) + - [Routines for updating validator status](#routines-for-updating-validator-status) - [Per-slot processing](#per-slot-processing) - [Misc counters](#misc-counters) - [Block roots](#block-roots) @@ -94,17 +93,17 @@ - [Slot](#slot) - [Proposer signature](#proposer-signature) - [RANDAO](#randao) - - [PoW receipt root](#pow-receipt-root) + - [Deposit root](#deposit-root) - [Operations](#operations) - [Proposer slashings](#proposer-slashings-1) - [Casper slashings](#casper-slashings-1) - [Attestations](#attestations-1) - [Deposits](#deposits-1) - [Exits](#exits-1) - - [Miscellaneous](#miscellaneous) + - [Custody](#custody) - [Per-epoch processing](#per-epoch-processing) - [Helpers](#helpers) - - [Receipt roots](#receipt-roots) + - [Deposit roots](#deposit-roots) - [Justification](#justification) - [Crosslinks](#crosslinks) - [Rewards and penalties](#rewards-and-penalties) @@ -113,7 +112,6 @@ - [Crosslinks](#crosslinks-1) - [Ejections](#ejections) - [Validator registry](#validator-registry) - - [Proposer reshuffling](#proposer-reshuffling) - [Final updates](#final-updates) - [State root processing](#state-root-processing) - [References](#references) @@ -127,7 +125,7 @@ This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain. -At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of [validators](#dfn-validator). In the initial deployment phases of Ethereum 2.0 the only mechanism to become a [validator](#dfn-validator) is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a [validator](#dfn-validator) happens when deposit transaction receipts are processed by the beacon chain, the activation balance is reached, and after a queuing process. Exit is either voluntary or done forcibly as a penalty for misbehavior. +At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of [validators](#dfn-validator). In the initial deployment phases of Ethereum 2.0 the only mechanism to become a [validator](#dfn-validator) is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a [validator](#dfn-validator) happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and after a queuing process. Exit is either voluntary or done forcibly as a penalty for misbehavior. The primary source of load on the beacon chain is "attestations". Attestations are availability votes for a shard block, and simultaneously proof of stake votes for a beacon chain block. A sufficient number of attestations for the same shard block create a "crosslink", confirming the shard segment up to that shard block into the beacon chain. Crosslinks also serve as infrastructure for asynchronous cross-shard communication. @@ -164,11 +162,10 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted | `MAX_BALANCE_CHURN_QUOTIENT` | `2**5` (= 32) | - | | `GWEI_PER_ETH` | `10**9` | Gwei/ETH | | `BEACON_CHAIN_SHARD_NUMBER` | `2**64 - 1` | - | -| `BLS_WITHDRAWAL_PREFIX_BYTE` | `0x00` | - | | `MAX_CASPER_VOTES` | `2**10` (= 1,024) | votes | | `LATEST_BLOCK_ROOTS_LENGTH` | `2**13` (= 8,192) | block roots | | `LATEST_RANDAO_MIXES_LENGTH` | `2**13` (= 8,192) | randao mixes | -| `EMPTY_SIGNATURE` | `[bytes48(0), bytes48(0)]` | - | +| `LATEST_PENALIZED_EXIT_LENGTH` | `2**13` (= 8,192) | epochs | ~36 days | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `EPOCH_LENGTH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) @@ -185,10 +182,13 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted | Name | Value | | - | - | -| `INITIAL_FORK_VERSION` | `0` | -| `INITIAL_SLOT_NUMBER` | `0` | +| `GENESIS_FORK_VERSION` | `0` | +| `GENESIS_SLOT` | `0` | | `GENESIS_START_SHARD` | `0` | -| `ZERO_HASH` | `bytes([0] * 32)` | +| `FAR_FUTURE_SLOT` | `2**64 - 1` | +| `ZERO_HASH` | `bytes32(0)` | +| `EMPTY_SIGNATURE` | `[bytes48(0), bytes48(0)]` | +| `BLS_WITHDRAWAL_PREFIX_BYTE` | `bytes1(0)` | ### Time parameters @@ -197,9 +197,10 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted | `SLOT_DURATION` | `6` | seconds | 6 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `2**2` (= 4) | slots | 24 seconds | | `EPOCH_LENGTH` | `2**6` (= 64) | slots | 6.4 minutes | -| `POW_RECEIPT_ROOT_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~1.7 hours | -| `COLLECTIVE_PENALTY_CALCULATION_PERIOD` | `2**20` (= 1,048,576) | slots | ~73 days | -| `ZERO_BALANCE_VALIDATOR_TTL` | `2**22` (= 4,194,304) | slots | ~291 days | +| `SEED_LOOKAHEAD` | `2**6` (= 64) | slots | 6.4 minutes | +| `ENTRY_EXIT_DELAY` | `2**8` (= 256) | slots | 25.6 minutes | +| `DEPOSIT_ROOT_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~1.7 hours | +| `MIN_VALIDATOR_WITHDRAWAL_TIME` | `2**14` (= 16,384) | slots | ~27 hours | ### Reward and penalty quotients @@ -213,15 +214,12 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted * The `BASE_REWARD_QUOTIENT` parameter 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 `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1-1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1-1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. -### Status codes +### Status flags | Name | Value | | - | - | -| `PENDING_ACTIVATION` | `0` | -| `ACTIVE` | `1` | -| `ACTIVE_PENDING_EXIT` | `2` | -| `EXITED_WITHOUT_PENALTY` | `3` | -| `EXITED_WITH_PENALTY` | `4` | +| `INITIATED_EXIT` | `2**0` (= 1) | +| `WITHDRAWABLE` | `2**1` (= 2) | ### Max operations per block @@ -289,14 +287,14 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted ```python { - # Proof-of-custody indices (0 bits) - 'aggregate_signature_poc_0_indices': '[uint24]', - # Proof-of-custody indices (1 bits) - 'aggregate_signature_poc_1_indices': '[uint24]', + # Validator indices with custody bit equal to 0 + 'custody_bit_0_indices': ['uint24'], + # Validator indices with custody bit equal to 1 + 'custody_bit_1_indices': ['uint24'], # Attestation data 'data': AttestationData, # Aggregate signature - 'aggregate_signature': '[uint384]', + 'aggregate_signature': ['uint384'], } ``` @@ -310,7 +308,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'data': AttestationData, # Attester participation bitfield 'participation_bitfield': 'bytes', - # Proof of custody bitfield + # Custody bitfield 'custody_bitfield': 'bytes', # BLS aggregate signature 'aggregate_signature': ['uint384'], @@ -346,8 +344,8 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted { # Attestation data data: AttestationData, - # Proof of custody bit - poc_bit: bool, + # Custody bit + custody_bit: bool, } ``` @@ -370,10 +368,10 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted ```python { - # Deposit parameters + # Deposit input 'deposit_input': DepositInput, - # Value in Gwei - 'value': 'uint64', + # Amount in Gwei + 'amount': 'uint64', # Timestamp from deposit contract 'timestamp': 'uint64', } @@ -389,9 +387,9 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'withdrawal_credentials': 'hash32', # Initial RANDAO commitment 'randao_commitment': 'hash32', - # Initial proof of custody commitment - 'poc_commitment': 'hash32', - # a BLS signature of this ``DepositInput`` + # Initial custody commitment + 'custody_commitment': 'hash32', + # A BLS signature of this `DepositInput` 'proof_of_possession': ['uint384'], } ``` @@ -422,7 +420,7 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'parent_root': 'hash32', 'state_root': 'hash32', 'randao_reveal': 'hash32', - 'candidate_pow_receipt_root': 'hash32', + 'deposit_root': 'hash32', 'signature': ['uint384'], ## Body ## @@ -437,15 +435,15 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'proposer_slashings': [ProposerSlashing], 'casper_slashings': [CasperSlashing], 'attestations': [Attestation], - 'poc_seed_changes': [ProofOfCustodySeedChange], - 'poc_challenges': [ProofOfCustodyChallenge], - 'poc_responses': [ProofOfCustodyResponse], + 'custody_reseeds': [CustodyReseed], + 'custody_challenges': [CustodyChallenge], + 'custody_responses': [CustodyResponse], 'deposits': [Deposit], 'exits': [Exit], } ``` -`ProofOfCustodySeedChange`, `ProofOfCustodyChallenge`, and `ProofOfCustodyResponse` will be defined in phase 1; for now, put dummy classes as these lists will remain empty throughout phase 0. +`CustodyReseed`, `CustodyChallenge`, and `CustodyResponse` will be defined in phase 1; for now, put dummy classes as these lists will remain empty throughout phase 0. #### `ProposalSignedData` @@ -483,10 +481,8 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'latest_vdf_outputs': ['hash32'], 'shard_committees_at_slots': [[ShardCommittee]], - # Proof of custody - # Placeholders for now; ProofOfCustodyChallenge is defined in phase 1, implementers can - # put a dummy class in for now, as the list will remain empty throughout phase 0 - 'poc_challenges': [ProofOfCustodyChallenge], + # Custody challenges + 'custody_challenges': [CustodyChallenge], # Finality 'previous_justified_slot': 'uint64', @@ -501,9 +497,9 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'latest_attestations': [PendingAttestationRecord], 'batched_block_roots': ['hash32'], - # PoW receipt root - 'processed_pow_receipt_root': 'hash32', - 'candidate_pow_receipt_roots': [CandidatePoWReceiptRootRecord], + # Ethereum 1.0 deposit root + 'latest_deposit_root': 'hash32', + 'deposit_root_votes': [DepositRootVote], } ``` @@ -519,17 +515,24 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'randao_commitment': 'hash32', # Slots the proposer has skipped (i.e. layers of RANDAO expected) 'randao_layers': 'uint64', - # Status code - 'status': 'uint64', - # Slot when validator last changed status (or 0) - 'latest_status_change_slot': 'uint64', - # Exit counter when validator exited (or 0) + # Slot when validator activated + 'activation_slot': 'uint64', + # Slot when validator exited + 'exit_slot': 'uint64', + # Slot when validator withdrew + 'withdrawal_slot': 'uint64', + # Slot when validator was penalized + 'penalized_slot': 'uint64', + # Exit counter when validator exited 'exit_count': 'uint64', - # Proof of custody commitment - 'poc_commitment': 'hash32', - # Slot the proof of custody seed was last changed - 'last_poc_change_slot': 'uint64', - 'second_last_poc_change_slot': 'uint64', + # Status flags + 'status_flags': 'uint64', + # Custody commitment + 'custody_commitment': 'hash32', + # Slot of latest custody reseed + 'latest_custody_reseed_slot': 'uint64', + # Slot of second-latest custody reseed + 'penultimate_custody_reseed_slot': 'uint64', } ``` @@ -552,30 +555,17 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'shard': 'uint64', # Validator indices 'committee': ['uint24'], - # Total validator count (for proofs of custody) + # Total validator count (for custody challenges) 'total_validator_count': 'uint64', } ``` -#### `ShardReassignmentRecord` +#### `DepositRootVote` ```python { - # Which validator to reassign - 'validator_index': 'uint24', - # To which shard - 'shard': 'uint64', - # When - 'slot': 'uint64', -} -``` - -#### `CandidatePoWReceiptRootRecord` - -```python -{ - # Candidate PoW receipt root - 'candidate_pow_receipt_root': 'hash32', + # Deposit root + 'deposit_root': 'hash32', # Vote count 'vote_count': 'uint64', } @@ -589,9 +579,9 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted 'data': AttestationData, # Attester participation bitfield 'participation_bitfield': 'bytes', - # Proof of custody bitfield + # Custody bitfield 'custody_bitfield': 'bytes', - # Slot in which it was included + # Slot the attestation was included 'slot_included': 'uint64', } ``` @@ -616,34 +606,39 @@ Unless otherwise indicated, code appearing in `this style` is to be interpreted latest_registry_delta_root: 'hash32', validator_index: 'uint24', pubkey: 'uint384', + slot: 'uint64', flag: 'uint64', } ``` ## Ethereum 1.0 deposit contract -The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to Ethereum 1.0 for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards when the EVM2.0 is deployed and the shards have state. +The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to Ethereum 1.0 for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in phase 2, i.e. when the EVM2.0 is deployed and the shards have state. ### Deposit arguments -The deposit contract has a single `deposit` function which takes as argument a SimpleSerialize'd `DepositInput`. One of the `DepositInput` fields is `withdrawal_credentials` which must satisfy: +The deposit contract has a single `deposit` function which takes as argument a SimpleSerialize'd `DepositInput`. + +### Withdrawal credentials + +One of the `DepositInput` fields is `withdrawal_credentials`. It is a commitment to credentials for withdrawals to shards. The first byte of `withdrawal_credentials` is a version number. As of now the only expected format is as follows: * `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE` * `withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:]` where `withdrawal_pubkey` is a BLS pubkey -We recommend the private key corresponding to `withdrawal_pubkey` be stored in cold storage until a withdrawal is required. +The private key corresponding to `withdrawal_pubkey` will be required to initiate a withdrawal. It can be stored separately until a withdrawal is required, e.g. in cold storage. -### `Eth1Deposit` logs +### `Deposit` logs -Every deposit, of size between `MIN_DEPOSIT` and `MAX_DEPOSIT`, emits an `Eth1Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. +Every Ethereum 1.0 deposit, of size between `MIN_DEPOSIT` and `MAX_DEPOSIT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. ### `ChainStart` log When sufficiently many full deposits have been made the deposit contract emits the `ChainStart` log. The beacon chain state may then be initialized by calling the `get_initial_beacon_state` function (defined below) where: * `genesis_time` equals `time` in the `ChainStart` log -* `processed_pow_receipt_root` equals `receipt_root` in the `ChainStart` log -* `initial_validator_deposits` is a list of `Deposit` objects built according to the `Eth1Deposit` logs up to the deposit that triggered the `ChainStart` log, processed in the order in which they were emitted (oldest to newest) +* `latest_deposit_root` equals `deposit_root` in the `ChainStart` log +* `initial_validator_deposits` is a list of `Deposit` objects built according to the `Deposit` logs up to the deposit that triggered the `ChainStart` log, processed in the order in which they were emitted (oldest to newest) ### Vyper code @@ -658,10 +653,10 @@ DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32 TWO_TO_POWER_OF_TREE_DEPTH: constant(uint256) = 4294967296 # 2**32 SECONDS_PER_DAY: constant(uint256) = 86400 -Eth1Deposit: event({previous_receipt_root: bytes32, data: bytes[2064], deposit_count: uint256}) -ChainStart: event({receipt_root: bytes32, time: bytes[8]}) +Deposit: event({previous_deposit_root: bytes32, data: bytes[2064], deposit_count: uint256}) +ChainStart: event({deposit_root: bytes32, time: bytes[8]}) -receipt_tree: map(uint256, bytes32) +deposit_tree: map(uint256, bytes32) deposit_count: uint256 full_deposit_count: uint256 @@ -676,13 +671,13 @@ def deposit(deposit_input: bytes[2048]): timestamp_bytes8: bytes[8] = slice(concat("", convert(block.timestamp, bytes32)), start=24, len=8) deposit_data: bytes[2064] = concat(msg_gwei_bytes8, timestamp_bytes8, deposit_input) - log.Eth1Deposit(self.receipt_tree[1], deposit_data, self.deposit_count) + log.Deposit(self.deposit_tree[1], deposit_data, self.deposit_count) # add deposit to merkle tree - self.receipt_tree[index] = sha3(deposit_data) + self.deposit_tree[index] = sha3(deposit_data) for i in range(32): # DEPOSIT_CONTRACT_TREE_DEPTH (range of constant var not yet supported) index /= 2 - self.receipt_tree[index] = sha3(concat(self.receipt_tree[index * 2], self.receipt_tree[index * 2 + 1])) + self.deposit_tree[index] = sha3(concat(self.deposit_tree[index * 2], self.deposit_tree[index * 2 + 1])) self.deposit_count += 1 if msg.value == as_wei_value(MAX_DEPOSIT, "ether"): @@ -690,12 +685,12 @@ def deposit(deposit_input: bytes[2048]): if self.full_deposit_count == CHAIN_START_FULL_DEPOSIT_THRESHOLD: timestamp_day_boundary: uint256 = as_unitless_number(block.timestamp) - as_unitless_number(block.timestamp) % SECONDS_PER_DAY + SECONDS_PER_DAY timestamp_day_boundary_bytes8: bytes[8] = slice(concat("", convert(timestamp_day_boundary, bytes32)), start=24, len=8) - log.ChainStart(self.receipt_tree[1], timestamp_day_boundary_bytes8) + log.ChainStart(self.deposit_tree[1], timestamp_day_boundary_bytes8) @public @constant -def get_receipt_root() -> bytes32: - return self.receipt_tree[1] +def get_deposit_root() -> bytes32: + return self.deposit_tree[1] ``` @@ -713,7 +708,7 @@ For a beacon chain block, `block`, to be processed by a node, the following cond * The parent block with root `block.parent_root` 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. +* An Ethereum 1.0 block pointed to by the `state.latest_deposit_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`. If these conditions are not met, the client should delay processing the beacon block until the conditions are all satisfied. @@ -736,7 +731,7 @@ The beacon chain fork choice rule is a hybrid that combines justification and fi def lmd_ghost(store, start): validators = start.state.validator_registry active_validators = [validators[i] for i in - get_active_validator_indices(validators)] + get_active_validator_indices(validators, start.state.slot)] attestation_targets = [get_latest_attestation_target(store, validator) for validator in active_validators] def get_vote_count(block): @@ -776,21 +771,21 @@ Note: We aim to migrate to a S[T/N]ARK-friendly hash function in a future Ethere #### `is_active_validator` ```python -def is_active_validator(validator: ValidatorRecord) -> bool: +def is_active_validator(validator: ValidatorRecord, slot: int) -> bool: """ Checks if ``validator`` is active. """ - return validator.status in [ACTIVE, ACTIVE_PENDING_EXIT] + return validator.activation_slot <= slot < validator.exit_slot ``` #### `get_active_validator_indices` ```python -def get_active_validator_indices(validators: [ValidatorRecord]) -> List[int]: +def get_active_validator_indices(validators: [ValidatorRecord], slot: int) -> List[int]: """ Gets indices of active validators from ``validators``. """ - return [i for i, v in enumerate(validators) if is_active_validator(v)] + return [i for i, v in enumerate(validators) if is_active_validator(v, slot)] ``` #### `shuffle` @@ -860,23 +855,27 @@ def split(values: List[Any], split_count: int) -> List[Any]: ] ``` -#### `get_new_shuffling` +#### `get_shuffling` ```python -def get_new_shuffling(randao_mix: Hash32, - slot: int, - validators: List[ValidatorRecord], - crosslinking_start_shard: int) -> List[List[ShardCommittee]]: +def get_shuffling(seed: Hash32, + validators: List[ValidatorRecord], + crosslinking_start_shard: int, + slot: int) -> List[List[ShardCommittee]]: """ Shuffles ``validators`` into shard committees seeded by ``randao_mix`` and ``slot``. """ - active_validator_indices = get_active_validator_indices(validators) + + # Normalizes slot to start of epoch boundary + slot -= slot % EPOCH_LENGTH + + active_validator_indices = get_active_validator_indices(validators, slot) committees_per_slot = max( 1, min( SHARD_COUNT // EPOCH_LENGTH, - len(active_validator_indices) // EPOCH_LENGTH // TARGET_COMMITTEE_SIZE, + len(active_validator_indices) // EPOCH_LENGTH // TARGET_COMMITTEE_SIZE, ) ) @@ -907,6 +906,8 @@ def get_new_shuffling(randao_mix: Hash32, return output ``` +**Invariant**: if `get_shuffling(seed, validators, shard, slot)` returns some value `x`, it should return the same value `x` for the same `seed` and `shard` and possible future modifications of `validators` forever in phase 0, and until the ~1 year deletion delay in phase 2 and in the future. + Here's a diagram of what is going on: ![](http://vitalik.ca/files/ShuffleAndAssign.png?1) @@ -954,7 +955,7 @@ def get_beacon_proposer_index(state: BeaconState, #### `merkle_root` ```python -def merkle_root(values): +def merkle_root(values): """ Merkleize ``values`` (where ``len(values)`` is a power of two) and return the Merkle root. """ @@ -1002,26 +1003,6 @@ def get_effective_balance(state: State, index: int) -> int: return min(state.validator_balances[index], MAX_DEPOSIT * GWEI_PER_ETH) ``` -#### `get_new_validator_registry_delta_chain_tip` - -```python -def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32, - validator_index: int, - pubkey: int, - flag: int) -> Hash32: - """ - Compute the next root in the validator registry delta chain. - """ - return hash_tree_root( - ValidatorRegistryDeltaBlock( - latest_registry_delta_root=current_validator_registry_delta_chain_tip, - validator_index=validator_index, - pubkey=pubkey, - flag=flag, - ) - ) -``` - #### `get_fork_version` ```python @@ -1049,17 +1030,17 @@ def get_domain(fork_data: ForkData, ```python def verify_slashable_vote_data(state: BeaconState, vote_data: SlashableVoteData) -> bool: - if len(vote_data.aggregate_signature_poc_0_indices) + len(vote_data.aggregate_signature_poc_1_indices) > MAX_CASPER_VOTES: + if len(vote_data.custody_bit_0_indices) + len(vote_data.custody_bit_1_indices) > MAX_CASPER_VOTES: return False return bls_verify_multiple( pubkeys=[ - aggregate_pubkey([state.validators[i].pubkey for i in vote_data.aggregate_signature_poc_0_indices]), - aggregate_pubkey([state.validators[i].pubkey for i in vote_data.aggregate_signature_poc_1_indices]), + aggregate_pubkey([state.validators[i].pubkey for i in vote_data.custody_bit_0_indices]), + aggregate_pubkey([state.validators[i].pubkey for i in vote_data.custody_bit_1_indices]), ], messages=[ - hash_tree_root(AttestationDataAndCustodyBit(vote_data, False)), - hash_tree_root(AttestationDataAndCustodyBit(vote_data, True)), + hash_tree_root(AttestationDataAndCustodyBit(vote_data.data, False)), + hash_tree_root(AttestationDataAndCustodyBit(vote_data.data, True)), ], signature=vote_data.aggregate_signature, domain=get_domain( @@ -1137,23 +1118,23 @@ 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 (e.g. requiring a signature) do not apply. +A valid block with slot `GENESIS_SLOT` (a "genesis block") has the following values. Other validity rules (e.g. requiring a signature) do not apply. ```python { - slot=INITIAL_SLOT_NUMBER, + slot=GENESIS_SLOT, parent_root=ZERO_HASH, state_root=STARTUP_STATE_ROOT, randao_reveal=ZERO_HASH, - candidate_pow_receipt_root=ZERO_HASH, + deposit_root=ZERO_HASH, signature=EMPTY_SIGNATURE, body=BeaconBlockBody( proposer_slashings=[], casper_slashings=[], attestations=[], - poc_seed_changes=[], - poc_challenges=[], - poc_responses=[], + custody_reseeds=[], + custody_challenges=[], + custody_responses=[], deposits=[], exits=[], ), @@ -1165,21 +1146,21 @@ A valid block with slot `INITIAL_SLOT_NUMBER` (a "genesis block") has the follow ```python def get_initial_beacon_state(initial_validator_deposits: List[Deposit], genesis_time: int, - processed_pow_receipt_root: Hash32) -> BeaconState: + latest_deposit_root: Hash32) -> BeaconState: state = BeaconState( # Misc - slot=INITIAL_SLOT_NUMBER, + slot=GENESIS_SLOT, genesis_time=genesis_time, fork_data=ForkData( - pre_fork_version=INITIAL_FORK_VERSION, - post_fork_version=INITIAL_FORK_VERSION, - fork_slot=INITIAL_SLOT_NUMBER, + pre_fork_version=GENESIS_FORK_VERSION, + post_fork_version=GENESIS_FORK_VERSION, + fork_slot=GENESIS_SLOT, ), # Validator registry validator_registry=[], validator_balances=[], - validator_registry_latest_change_slot=INITIAL_SLOT_NUMBER, + validator_registry_latest_change_slot=GENESIS_SLOT, validator_registry_exit_count=0, validator_registry_delta_chain_tip=ZERO_HASH, @@ -1188,43 +1169,46 @@ def get_initial_beacon_state(initial_validator_deposits: List[Deposit], latest_vdf_outputs=[ZERO_HASH for _ in range(LATEST_RANDAO_MIXES_LENGTH // EPOCH_LENGTH)], shard_committees_at_slots=[], - # Proof of custody - poc_challenges=[], + # Custody challenges + custody_challenges=[], # Finality - previous_justified_slot=INITIAL_SLOT_NUMBER, - justified_slot=INITIAL_SLOT_NUMBER, + previous_justified_slot=GENESIS_SLOT, + justified_slot=GENESIS_SLOT, justification_bitfield=0, - finalized_slot=INITIAL_SLOT_NUMBER, + finalized_slot=GENESIS_SLOT, # Recent state - latest_crosslinks=[CrosslinkRecord(slot=INITIAL_SLOT_NUMBER, shard_block_root=ZERO_HASH) for _ in range(SHARD_COUNT)], + latest_crosslinks=[CrosslinkRecord(slot=GENESIS_SLOT, shard_block_root=ZERO_HASH) for _ in range(SHARD_COUNT)], latest_block_roots=[ZERO_HASH for _ in range(LATEST_BLOCK_ROOTS_LENGTH)], - latest_penalized_exit_balances=[], + latest_penalized_exit_balances=[0 for _ in LATEST_PENALIZED_EXIT_LENGTH], latest_attestations=[], batched_block_roots=[], - # PoW receipt root - processed_pow_receipt_root=processed_pow_receipt_root, - candidate_pow_receipt_roots=[], + # Deposit root + latest_deposit_root=latest_deposit_root, + deposit_root_votes=[], ) - # handle initial deposits and activations + # Process initial deposits for deposit in initial_validator_deposits: - validator_index = process_deposit( + process_deposit( state=state, pubkey=deposit.deposit_data.deposit_input.pubkey, - deposit=deposit.deposit_data.value, + amount=deposit.deposit_data.amount, proof_of_possession=deposit.deposit_data.deposit_input.proof_of_possession, withdrawal_credentials=deposit.deposit_data.deposit_input.withdrawal_credentials, randao_commitment=deposit.deposit_data.deposit_input.randao_commitment, - poc_commitment=deposit.deposit_data.deposit_input.poc_commitment, + custody_commitment=deposit.deposit_data.deposit_input.custody_commitment, ) - if get_effective_balance(state, validator_index) == MAX_DEPOSIT * GWEI_PER_ETH: - update_validator_status(state, validator_index, ACTIVE) - # set initial committee shuffling - initial_shuffling = get_new_shuffling(ZERO_HASH, state.slot, state.validator_registry, GENESIS_START_SHARD) + # Process initial activations + for validator_index, _ in enumerate(state.validator_registry): + if get_effective_balance(state, validator_index) >= MAX_DEPOSIT * GWEI_PER_ETH: + activate_validator(state, validator_index, True) + + # Set initial committee shuffling + initial_shuffling = get_shuffling(ZERO_HASH, state.validator_registry, GENESIS_START_SHARD, GENESIS_SLOT) state.shard_committees_at_slots = initial_shuffling + initial_shuffling return state @@ -1232,17 +1216,7 @@ def get_initial_beacon_state(initial_validator_deposits: List[Deposit], ### Routine for processing deposits -First, two helper functions: - -```python -def min_empty_validator_index(validators: List[ValidatorRecord], - validator_balances: List[int], - current_slot: int) -> int: - for i, (v, vbal) in enumerate(zip(validators, validator_balances)): - if vbal == 0 and v.latest_status_change_slot + ZERO_BALANCE_VALIDATOR_TTL <= current_slot: - return i - return None -``` +First, a helper function: ```python def validate_proof_of_possession(state: BeaconState, @@ -1250,12 +1224,12 @@ def validate_proof_of_possession(state: BeaconState, proof_of_possession: bytes, withdrawal_credentials: Hash32, randao_commitment: Hash32, - poc_commitment: Hash32) -> bool: + custody_commitment: Hash32) -> bool: proof_of_possession_data = DepositInput( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, - poc_commitment=poc_commitment, + custody_commitment=custody_commitment, proof_of_possession=EMPTY_SIGNATURE, ) @@ -1276,11 +1250,11 @@ Now, to add a [validator](#dfn-validator) or top up an existing [validator](#dfn ```python def process_deposit(state: BeaconState, pubkey: int, - deposit: int, + amount: int, proof_of_possession: bytes, withdrawal_credentials: Hash32, randao_commitment: Hash32, - poc_commitment: Hash32) -> int: + custody_commitment: Hash32) -> None: """ Process a deposit from Ethereum 1.0. Note that this function mutates ``state``. @@ -1292,7 +1266,7 @@ def process_deposit(state: BeaconState, proof_of_possession, withdrawal_credentials, randao_commitment, - poc_commitment, + custody_commitment, ) validator_pubkeys = [v.pubkey for v in state.validator_registry] @@ -1304,128 +1278,97 @@ def process_deposit(state: BeaconState, withdrawal_credentials=withdrawal_credentials, randao_commitment=randao_commitment, randao_layers=0, - status=PENDING_ACTIVATION, - latest_status_change_slot=state.slot, + activation_slot=FAR_FUTURE_SLOT, + exit_slot=FAR_FUTURE_SLOT, + withdrawal_slot=FAR_FUTURE_SLOT, + penalized_slot=FAR_FUTURE_SLOT, exit_count=0, - poc_commitment=poc_commitment, - last_poc_change_slot=0, - second_last_poc_change_slot=0, + status_flags=0, + custody_commitment=custody_commitment, + latest_custody_reseed_slot=GENESIS_SLOT, + penultimate_custody_reseed_slot=GENESIS_SLOT, ) - index = min_empty_validator_index(state.validator_registry, state.validator_balances, state.slot) - if index is None: - state.validator_registry.append(validator) - state.validator_balances.append(deposit) - index = len(state.validator_registry) - 1 - else: - state.validator_registry[index] = validator - state.validator_balances[index] = deposit + # Note: In phase 2 registry indices that has been withdrawn for a long time will be recycled. + index = len(state.validator_registry) + state.validator_registry.append(validator) + state.validator_balances.append(amount) else: - # Increase balance by deposit + # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) assert state.validator_registry[index].withdrawal_credentials == withdrawal_credentials - state.validator_balances[index] += deposit - - return index + state.validator_balances[index] += amount ``` -### Routine for updating validator status +### Routines for updating validator status + +Note: All functions in this section mutate `state`. ```python -def update_validator_status(state: BeaconState, - index: int, - new_status: int) -> None: - """ - Update the validator status with the given ``index`` to ``new_status``. - Handle other general accounting related to this status update. - Note that this function mutates ``state``. - """ - if new_status == ACTIVE: - activate_validator(state, index) - if new_status == ACTIVE_PENDING_EXIT: - initiate_validator_exit(state, index) - if new_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY]: - exit_validator(state, index, new_status) -``` - -The following are helpers and should only be called via `update_validator_status`: - -```python -def activate_validator(state: BeaconState, - index: int) -> None: - """ - Activate the validator with the given ``index``. - Note that this function mutates ``state``. - """ +def activate_validator(state: BeaconState, index: int, genesis: bool) -> None: validator = state.validator_registry[index] - if validator.status != PENDING_ACTIVATION: - return - validator.status = ACTIVE - validator.latest_status_change_slot = state.slot - state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( - current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, - validator_index=index, - pubkey=validator.pubkey, - flag=ACTIVATION, + validator.activation_slot = GENESIS_SLOT if genesis else (state.slot + ENTRY_EXIT_DELAY) + state.validator_registry_delta_chain_tip = hash_tree_root( + ValidatorRegistryDeltaBlock( + current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, + validator_index=index, + pubkey=validator.pubkey, + slot=validator.activation_slot, + flag=ACTIVATION, + ) ) ``` ```python -def initiate_validator_exit(state: BeaconState, - index: int) -> None: - """ - Initiate exit for the validator with the given ``index``. - Note that this function mutates ``state``. - """ +def initiate_validator_exit(state: BeaconState, index: int) -> None: validator = state.validator_registry[index] - if validator.status != ACTIVE: - return - - validator.status = ACTIVE_PENDING_EXIT - validator.latest_status_change_slot = state.slot + validator.status_flags |= INITIATED_EXIT ``` ```python -def exit_validator(state: BeaconState, - index: int, - new_status: int) -> None: - """ - Exit the validator with the given ``index``. - Note that this function mutates ``state``. - """ +def exit_validator(state: BeaconState, index: int) -> None: validator = state.validator_registry[index] - prev_status = validator.status - - if prev_status == EXITED_WITH_PENALTY: - return - - validator.status = new_status - validator.latest_status_change_slot = state.slot - - if new_status == EXITED_WITH_PENALTY: - state.latest_penalized_exit_balances[state.slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD] += get_effective_balance(state, index) - - whistleblower_index = get_beacon_proposer_index(state, state.slot) - whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT - state.validator_balances[whistleblower_index] += whistleblower_reward - state.validator_balances[index] -= whistleblower_reward - - if prev_status == EXITED_WITHOUT_PENALTY: - return # The following updates only occur if not previous exited + if validator.exit_slot <= state.slot + ENTRY_EXIT_DELAY: + return + + validator.exit_slot = state.slot + ENTRY_EXIT_DELAY + state.validator_registry_exit_count += 1 validator.exit_count = state.validator_registry_exit_count - state.validator_registry_delta_chain_tip = get_new_validator_registry_delta_chain_tip( - current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, - validator_index=index, - pubkey=validator.pubkey, - flag=EXIT, + state.validator_registry_delta_chain_tip = hash_tree_root( + ValidatorRegistryDeltaBlock( + current_validator_registry_delta_chain_tip=state.validator_registry_delta_chain_tip, + validator_index=index, + pubkey=validator.pubkey, + slot=validator.exit_slot, + flag=EXIT, + ) ) ``` +```python +def penalize_validator(state: BeaconState, index: int) -> None: + exit_validator(state, index) + validator = state.validator_registry[index] + state.latest_penalized_exit_balances[(state.slot // EPOCH_LENGTH) % LATEST_PENALIZED_EXIT_LENGTH] += get_effective_balance(state, index) + + whistleblower_index = get_beacon_proposer_index(state, state.slot) + whistleblower_reward = get_effective_balance(state, index) // WHISTLEBLOWER_REWARD_QUOTIENT + state.validator_balances[whistleblower_index] += whistleblower_reward + state.validator_balances[index] -= whistleblower_reward + validator.penalized_slot = state.slot +``` + +```python +def prepare_validator_for_withdrawal(state: BeaconState, index: int) -> None: + validator = state.validator_registry[index] + validator.status_flags |= WITHDRAWABLE +``` + ## Per-slot processing Below are the processing steps that happen at every slot. @@ -1465,10 +1408,10 @@ Below are the processing steps that happen at every `block`. * Set `proposer.randao_commitment = block.randao_reveal`. * Set `proposer.randao_layers = 0`. -### PoW receipt root +### Deposit root -* If `block.candidate_pow_receipt_root` is `x.candidate_pow_receipt_root` for some `x` in `state.candidate_pow_receipt_roots`, set `x.vote_count += 1`. -* Otherwise, append to `state.candidate_pow_receipt_roots` a new `CandidatePoWReceiptRootRecord(candidate_pow_receipt_root=block.candidate_pow_receipt_root, vote_count=1)`. +* If `block.deposit_root` is `deposit_root_vote.deposit_root` for some `deposit_root_vote` in `state.deposit_root_votes`, set `deposit_root_vote.vote_count += 1`. +* Otherwise, append to `state.deposit_root_votes` a new `DepositRootVote(deposit_root=block.deposit_root, vote_count=1)`. ### Operations @@ -1482,10 +1425,10 @@ For each `proposer_slashing` in `block.body.proposer_slashings`: * Verify that `proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot`. * Verify that `proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard`. * Verify that `proposer_slashing.proposal_data_1.block_root != proposer_slashing.proposal_data_2.block_root`. -* Verify that `proposer.status != EXITED_WITH_PENALTY`. +* Verify that `validator.penalized_slot > state.slot`. * Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_1), signature=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_1.slot, DOMAIN_PROPOSAL))`. * Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_2), signature=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork_data, proposer_slashing.proposal_data_2.slot, DOMAIN_PROPOSAL))`. -* Run `update_validator_status(state, proposer_slashing.proposer_index, new_status=EXITED_WITH_PENALTY)`. +* Run `penalize_validator(state, proposer_slashing.proposer_index)`. #### Casper slashings @@ -1495,14 +1438,14 @@ For each `casper_slashing` in `block.body.casper_slashings`: * Let `slashable_vote_data_1 = casper_slashing.slashable_vote_data_1`. * Let `slashable_vote_data_2 = casper_slashing.slashable_vote_data_2`. -* Let `indices(slashable_vote_data) = slashable_vote_data.aggregate_signature_poc_0_indices + slashable_vote_data.aggregate_signature_poc_1_indices`. +* Let `indices(slashable_vote_data) = slashable_vote_data.custody_bit_0_indices + slashable_vote_data.custody_bit_1_indices`. * Let `intersection = [x for x in indices(slashable_vote_data_1) if x in indices(slashable_vote_data_2)]`. * Verify that `len(intersection) >= 1`. * Verify that `slashable_vote_data_1.data != slashable_vote_data_2.data`. * Verify that `is_double_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)` or `is_surround_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)`. * Verify that `verify_slashable_vote_data(state, slashable_vote_data_1)`. * Verify that `verify_slashable_vote_data(state, slashable_vote_data_2)`. -* For each [validator](#dfn-validator) index `i` in `intersection`, if `state.validator_registry[i].status` does not equal `EXITED_WITH_PENALTY`, then run `update_validator_status(state, i, new_status=EXITED_WITH_PENALTY)` +* For each [validator](#dfn-validator) index `i` in `intersection` run `penalize_validator(state, i)` if `state.validator_registry[i].penalized_slot > state.slot`. #### Attestations @@ -1530,32 +1473,31 @@ Verify that `len(block.body.deposits) <= MAX_DEPOSITS`. For each `deposit` in `block.body.deposits`: -* Let `serialized_deposit_data` be the serialized form of `deposit.deposit_data`. It should be the `DepositInput` followed by 8 bytes for `deposit_data.value` and 8 bytes for `deposit_data.timestamp`. That is, it should match `deposit_data` in the [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) of which the hash was placed into the Merkle tree. -* Use the following procedure to verify `deposit.merkle_branch`, setting `leaf=serialized_deposit_data`, `depth=DEPOSIT_CONTRACT_TREE_DEPTH` and `root=state.processed_pow_receipt_root`: +* Let `serialized_deposit_data` be the serialized form of `deposit.deposit_data`. It should be the `DepositInput` followed by 8 bytes for `deposit_data.amount` and 8 bytes for `deposit_data.timestamp`. That is, it should match `deposit_data` in the [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) of which the hash was placed into the Merkle tree. +* Use the following procedure to verify `deposit.merkle_branch`, setting `leaf=serialized_deposit_data`, `depth=DEPOSIT_CONTRACT_TREE_DEPTH` and `root=state.latest_deposit_root`: ```python def verify_merkle_branch(leaf: Hash32, branch: [Hash32], depth: int, index: int, root: Hash32) -> bool: value = leaf for i in range(depth): - if index % 2: + if index // (2**i) % 2: value = hash(branch[i] + value) else: value = hash(value + branch[i]) return value == root ``` -* Verify that `state.slot - (deposit.deposit_data.timestamp - state.genesis_time) // SLOT_DURATION < ZERO_BALANCE_VALIDATOR_TTL`. * Run the following: ```python process_deposit( state=state, pubkey=deposit.deposit_data.deposit_input.pubkey, - deposit=deposit.deposit_data.value, + amount=deposit.deposit_data.amount, proof_of_possession=deposit.deposit_data.deposit_input.proof_of_possession, withdrawal_credentials=deposit.deposit_data.deposit_input.withdrawal_credentials, randao_commitment=deposit.deposit_data.deposit_input.randao_commitment, - poc_commitment=deposit.deposit_data.deposit_input.poc_commitment, + custody_commitment=deposit.deposit_data.deposit_input.custody_commitment, ) ``` @@ -1566,14 +1508,14 @@ Verify that `len(block.body.exits) <= MAX_EXITS`. For each `exit` in `block.body.exits`: * Let `validator = state.validator_registry[exit.validator_index]`. -* Verify that `validator.status == ACTIVE`. +* Verify that `validator.exit_slot > state.slot + ENTRY_EXIT_DELAY`. * Verify that `state.slot >= exit.slot`. * Verify that `bls_verify(pubkey=validator.pubkey, message=ZERO_HASH, signature=exit.signature, domain=get_domain(state.fork_data, exit.slot, DOMAIN_EXIT))`. -* Run `update_validator_status(state, validator_index, new_status=ACTIVE_PENDING_EXIT)`. +* Run `initiate_validator_exit(state, exit.validator_index)`. -#### Miscellaneous +#### Custody -[TO BE REMOVED IN PHASE 1] Verify that `len(block.body.poc_seed_changes) == len(block.body.poc_challenges) == len(block.body.poc_responses) == 0`. +[TO BE REMOVED IN PHASE 1] Verify that `len(block.body.custody_reseeds) == len(block.body.custody_challenges) == len(block.body.custody_responses) == 0`. ## Per-epoch processing @@ -1583,7 +1525,7 @@ The steps below happen when `state.slot % EPOCH_LENGTH == 0`. All [validators](#dfn-validator): -* Let `active_validator_indices = get_active_validator_indices(state.validator_registry)`. +* Let `active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot)`. * Let `total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices])`. [Validators](#dfn-Validator) attesting during the current epoch: @@ -1599,7 +1541,7 @@ All [validators](#dfn-validator): * Validators that made an attestation during 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_attester_indices` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_attestations]`. -* Validators targeting the previous justified hash: +* Validators targeting the previous justified slot: * Let `previous_epoch_justified_attestations = [a for a in current_epoch_attestations + previous_epoch_attestations if a.justified_slot == state.previous_justified_slot]`. * Let `previous_epoch_justified_attester_indices` be the union of the validator index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in previous_epoch_justified_attestations]`. * Let `previous_epoch_justified_attesting_balance = sum([get_effective_balance(state, i) for i in previous_epoch_justified_attester_indices])`. @@ -1614,8 +1556,9 @@ All [validators](#dfn-validator): **Note**: `previous_epoch_boundary_attesting_balance` balance might be marginally different than `current_epoch_boundary_attesting_balance` during the previous epoch transition. Due to the tight bound on validator churn each epoch and small per-epoch rewards/penalties, the potential balance difference is very low and only marginally affects consensus safety. -For every `shard_committee` in `state.shard_committees_at_slots`: +For every `shard_committee_at_slot` in `state.shard_committees_at_slots` and for every `shard_committee` in `shard_committee_at_slot`: +* Let `shard_block_root` be `state.latest_crosslinks[shard_committee.shard].shard_block_root` * Let `attesting_validator_indices(shard_committee, shard_block_root)` be the union of the [validator](#dfn-validator) index sets given by `[get_attestation_participants(state, a.data, a.participation_bitfield) for a in current_epoch_attestations + previous_epoch_attestations if a.shard == shard_committee.shard and a.shard_block_root == shard_block_root]`. * Let `winning_root(shard_committee)` be equal to the value of `shard_block_root` such that `sum([get_effective_balance(state, i) for i in attesting_validator_indices(shard_committee, shard_block_root)])` is maximized (ties broken by favoring lower `shard_block_root` values). * Let `attesting_validators(shard_committee)` be equal to `attesting_validator_indices(shard_committee, winning_root(shard_committee))` for convenience. @@ -1624,12 +1567,12 @@ For every `shard_committee` in `state.shard_committees_at_slots`: * Let `inclusion_slot(state, index) = a.slot_included` for the attestation `a` where `index` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`. * Let `inclusion_distance(state, index) = a.slot_included - a.data.slot` where `a` is the above attestation. -### Receipt roots +### Deposit roots -If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: +If `state.slot % DEPOSIT_ROOT_VOTING_PERIOD == 0`: -* Set `state.processed_pow_receipt_root = x.receipt_root` if `x.vote_count * 2 > POW_RECEIPT_ROOT_VOTING_PERIOD` for some `x` in `state.candidate_pow_receipt_root`. -* Set `state.candidate_pow_receipt_roots = []`. +* Set `state.latest_deposit_root = deposit_root_vote.deposit_root` if `deposit_root_vote.vote_count * 2 > DEPOSIT_ROOT_VOTING_PERIOD` for some `deposit_root_vote` in `state.deposit_root_votes`. +* Set `state.deposit_root_votes = []`. ### Justification @@ -1638,7 +1581,6 @@ If `state.slot % POW_RECEIPT_ROOT_VOTING_PERIOD == 0`: * 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 * current_epoch_boundary_attesting_balance >= 2 * total_balance`. - 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` @@ -1649,7 +1591,7 @@ Set `state.finalized_slot = state.previous_justified_slot` if any of the followi For every `shard_committee_at_slot` in `state.shard_committees_at_slots` and for every `shard_committee`in `shard_committee_at_slot`: -* Set `state.latest_crosslinks[shard_committee.shard] = CrosslinkRecord(slot=state.slot, block_root=winning_root(shard_committee))` if `3 * total_attesting_balance(shard_committee) >= 2 * total_balance(shard_committee)`. +* Set `state.latest_crosslinks[shard_committee.shard] = CrosslinkRecord(slot=state.slot, shard_block_root=winning_root(shard_committee))` if `3 * total_attesting_balance(shard_committee) >= 2 * total_balance(shard_committee)`. ### Rewards and penalties @@ -1684,7 +1626,7 @@ Case 2: `epochs_since_finality > 4`: * Any [active validator](#dfn-active-validator) `index` not in `previous_epoch_justified_attester_indices`, loses `inactivity_penalty(state, index, epochs_since_finality)`. * Any [active validator](#dfn-active-validator) `index` not in `previous_epoch_boundary_attester_indices`, loses `inactivity_penalty(state, index, epochs_since_finality)`. * Any [active validator](#dfn-active-validator) `index` not in `previous_epoch_head_attester_indices`, loses `base_reward(state, index)`. -* Any [validator](#dfn-validator) `index` with `status == EXITED_WITH_PENALTY`, loses `2 * inactivity_penalty(state, index, epochs_since_finality) + base_reward(state, index)`. +* Any [active_validator](#dfn-active-validator) `index` with `validator.penalized_slot <= state.slot`, loses `2 * inactivity_penalty(state, index, epochs_since_finality) + base_reward(state, index)`. * Any [validator](#dfn-validator) `index` in `previous_epoch_attester_indices` loses `base_reward(state, index) - base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_distance(state, index)` #### Attestation inclusion @@ -1709,8 +1651,8 @@ def process_ejections(state: BeaconState) -> None: and eject active validators with balance below ``EJECTION_BALANCE``. """ for index in active_validator_indices(state.validator_registry): - if state.validator_balances[index] < EJECTION_BALANCE: - update_validator_status(state, index, new_status=EXITED_WITHOUT_PENALTY) + if state.validator_balances[index] < EJECTION_BALANCE * GWEI_PER_ETH: + exit_validator(state, index) ``` ### Validator registry @@ -1718,7 +1660,7 @@ def process_ejections(state: BeaconState) -> None: 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` +* `state.latest_crosslinks[shard].slot > state.validator_registry_latest_change_slot` for every shard number `shard` in `shard_committee` from `shard_committee_at_slot` in `state.shard_committees_at_slots` update the validator registry and associated fields by running @@ -1729,7 +1671,7 @@ def update_validator_registry(state: BeaconState) -> None: Note that this function mutates ``state``. """ # The active validators - active_validator_indices = get_active_validator_indices(state.validator_registry) + active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) # The total effective balance of active validators total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices]) @@ -1742,61 +1684,83 @@ def update_validator_registry(state: BeaconState) -> None: # Activate validators within the allowable balance churn balance_churn = 0 for index, validator in enumerate(state.validator_registry): - if validator.status == PENDING_ACTIVATION and state.validator_balances[index] >= MAX_DEPOSIT * GWEI_PER_ETH: + if validator.activation_slot > state.slot + ENTRY_EXIT_DELAY and state.validator_balances[index] >= MAX_DEPOSIT * GWEI_PER_ETH: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index) if balance_churn > max_balance_churn: break # Activate validator - update_validator_status(state, index, new_status=ACTIVE) + activate_validator(state, index, False) # Exit validators within the allowable balance churn balance_churn = 0 for index, validator in enumerate(state.validator_registry): - if validator.status == ACTIVE_PENDING_EXIT: + if validator.exit_slot > state.slot + ENTRY_EXIT_DELAY and validator.status_flags & INITIATED_EXIT: # Check the balance churn would be within the allowance balance_churn += get_effective_balance(state, index) if balance_churn > max_balance_churn: break # Exit validator - update_validator_status(state, index, new_status=EXITED_WITHOUT_PENALTY) + exit_validator(state, index) + state.validator_registry_latest_change_slot = state.slot +``` - # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods - period_index = current_slot // COLLECTIVE_PENALTY_CALCULATION_PERIOD - total_penalties = ( - (latest_penalized_exit_balances[period_index]) + - (latest_penalized_exit_balances[period_index - 1] if period_index >= 1 else 0) + - (latest_penalized_exit_balances[period_index - 2] if period_index >= 2 else 0) - ) +Regardless of whether the above conditions are satisfied, run the following: - # Calculate penalties for slashed validators - def to_penalize(index): - return state.validator_registry[index].status == EXITED_WITH_PENALTY - validators_to_penalize = filter(to_penalize, range(len(validator_registry))) - for index in validators_to_penalize: - state.validator_balances[index] -= get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance +```python +def process_penalties_and_exits(state: BeaconState) -> None: + # The active validators + active_validator_indices = get_active_validator_indices(state.validator_registry, state.slot) + # The total effective balance of active validators + total_balance = sum([get_effective_balance(state, i) for i in active_validator_indices]) - return validator_registry, latest_penalized_exit_balances, validator_registry_delta_chain_tip + for index, validator in enumerate(state.validator_registry): + if (state.slot // EPOCH_LENGTH) == (validator.penalized_slot // EPOCH_LENGTH) + LATEST_PENALIZED_EXIT_LENGTH // 2: + e = (state.slot // EPOCH_LENGTH) % LATEST_PENALIZED_EXIT_LENGTH + total_at_start = state.latest_penalized_exit_balances[(e + 1) % LATEST_PENALIZED_EXIT_LENGTH] + total_at_end = state.latest_penalized_exit_balances[e] + total_penalties = total_at_end - total_at_start + penalty = get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance + state.validator_balances[index] -= penalty + + def eligible(index): + validator = state.validator_registry[index] + if validator.penalized_slot <= state.slot: + PENALIZED_WITHDRAWAL_TIME = LATEST_PENALIZED_EXIT_LENGTH * EPOCH_LENGTH // 2 + return state.slot >= validator.penalized_slot + PENALIZED_WITHDRAWAL_TIME + else: + return state.slot >= validator.exit_slot + MIN_VALIDATOR_WITHDRAWAL_TIME + + all_indices = list(range(len(state.validator_registry))) + eligible_indices = filter(eligible, all_indices) + sorted_indices = sorted(eligible_indices, key=lambda index: state.validator_registry[index].exit_count) + withdrawn_so_far = 0 + for index in sorted_indices: + validator = state.validator_registry[index] + prepare_validator_for_withdrawal(state, index) + withdrawn_so_far += 1 + if withdrawn_so_far >= MAX_WITHDRAWALS_PER_EPOCH: + break ``` 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.latest_randao_mixes[(state.slot - EPOCH_LENGTH) % LATEST_RANDAO_MIXES_LENGTH], state.slot, 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_shuffling(state.latest_randao_mixes[(state.slot - SEED_LOOKAHEAD) % LATEST_RANDAO_MIXES_LENGTH], state.validator_registry, next_start_shard, state.slot)` where `next_start_shard = (state.shard_committees_at_slots[-1][-1].shard + 1) % SHARD_COUNT`. 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 `epochs_since_last_registry_change = (state.slot - state.validator_registry_latest_change_slot) // EPOCH_LENGTH`. * Let `start_shard = state.shard_committees_at_slots[0][0].shard`. -* If `epochs_since_last_registry_change` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_new_shuffling(state.latest_randao_mixes[(state.slot - EPOCH_LENGTH) % LATEST_RANDAO_MIXES_LENGTH], state.slot, state.validator_registry, start_shard)`. Note that `start_shard` is not changed from the last epoch. +* If `epochs_since_last_registry_change` is an exact power of 2, set `state.shard_committees_at_slots[EPOCH_LENGTH:] = get_shuffling(state.latest_randao_mixes[(state.slot - SEED_LOOKAHEAD) % LATEST_RANDAO_MIXES_LENGTH], state.validator_registry, start_shard, state.slot)`. Note that `start_shard` is not changed from the last epoch. ### Final updates +* Let `e = state.slot // EPOCH_LENGTH`. Set `state.latest_penalized_exit_balances[(e+1) % LATEST_PENALIZED_EXIT_LENGTH] = state.latest_penalized_exit_balances[e % LATEST_PENALIZED_EXIT_LENGTH]` * Remove any `attestation` in `state.latest_attestations` such that `attestation.data.slot < state.slot - EPOCH_LENGTH`. ## State root processing diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index a66f37ac5..973a0d332 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -53,8 +53,8 @@ A `ShardBlock` object has the following fields: # Block signature 'signature': ['uint384'], # Attestation - 'attester_bitfield': 'bytes', - 'aggregate_sig': ['uint384'], + 'participation_bitfield': 'bytes', + 'aggregate_signature': ['uint384'], } ``` @@ -69,9 +69,9 @@ To validate a block header on shard `shard_id`, compute as follows: * Verify that `beacon_chain_ref` is the hash of a block in the beacon chain with slot less than or equal to `slot`. Verify that `beacon_chain_ref` is equal to or a descendant of the `beacon_chain_ref` specified in the `ShardBlock` pointed to by `parent_root`. * Let `state` be the state of the beacon chain block referred to by `beacon_chain_ref`. Let `validators` be `[validators[i] for i in state.current_persistent_committees[shard_id]]`. -* Assert `len(attester_bitfield) == ceil_div8(len(validators))` +* Assert `len(participation_bitfield) == ceil_div8(len(validators))` * Let `proposer_index = hash(state.randao_mix + bytes8(shard_id) + bytes8(slot)) % len(validators)`. Let `msg` be the block but with the `block.signature` set to `[0, 0]`. Verify that `BLSVerify(pub=validators[proposer_index].pubkey, msg=hash(msg), sig=block.signature, domain=get_domain(state, slot, SHARD_PROPOSER_DOMAIN))` passes. -* Generate the `group_public_key` by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify that `BLSVerify(pub=group_public_key, msg=parent_root, sig=block.aggregate_sig, domain=get_domain(state, slot, SHARD_ATTESTER_DOMAIN))` passes. +* Generate the `group_public_key` by adding the public keys of all the validators for whom the corresponding position in the bitfield is set to 1. Verify that `BLSVerify(pub=group_public_key, msg=parent_root, sig=block.aggregate_signature, domain=get_domain(state, slot, SHARD_ATTESTER_DOMAIN))` passes. ### Block Merklization helper diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index 63a4d4e31..03fc8ebab 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -248,10 +248,10 @@ size as the integer length. (e.g. ``uint16 == 2 bytes``) All integers are interpreted as **big endian**. ```python -assert(len(rawbytes) >= current_index + int_size) byte_length = int_size / 8 -new_index = current_index + int_size -return int.from_bytes(rawbytes[current_index:current_index+int_size], 'big'), new_index +new_index = current_index + byte_length +assert(len(rawbytes) >= new_index) +return int.from_bytes(rawbytes[current_index:current_index+byte_length], 'big'), new_index ``` #### Bool @@ -384,7 +384,7 @@ values = {} for field_name in get_field_names(typ): field_name_type = get_type_for_field_name(typ, field_name) values[field_name], item_index = deserialize(data, item_index, field_name_type) -assert item_index == start + LENGTH_BYTES + length +assert item_index == new_index return typ(**values), item_index ``` @@ -392,7 +392,7 @@ return typ(**values), item_index The below `hash_tree_root` algorithm is defined recursively in the case of lists and containers, and it outputs a value equal to or less than 32 bytes in size. For the final output only (ie. not intermediate outputs), if the output is less than 32 bytes, right-zero-pad it to 32 bytes. The goal is collision resistance *within* each type, not between types. -Refer to [Appendix A](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#appendix-a---hash-function) of Phase 0 of the [Eth2.0 specs](https://github.com/ethereum/eth2.0-specs) for a definition of the hash function used below, `hash(x)`. +Refer to [the helper function `hash`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#hash) of Phase 0 of the [Eth2.0 specs](https://github.com/ethereum/eth2.0-specs) for a definition of the hash function used below, `hash(x)`. #### `uint8`..`uint256`, `bool`, `address`, `hash1`..`hash32`