diff --git a/specs/core/1_beacon-chain.md b/specs/core/1_beacon-chain.md index 57bd1fcd7..94dfd624f 100644 --- a/specs/core/1_beacon-chain.md +++ b/specs/core/1_beacon-chain.md @@ -180,6 +180,32 @@ class Validator(Container): max_reveal_lateness: Epoch ``` +### New extended `BeaconBlockBody` + +```python +class BeaconBlockBody(phase0.BeaconBlockBody): + randao_reveal: BLSSignature + eth1_data: Eth1Data # Eth1 data vote + graffiti: Bytes32 # Arbitrary data + # Slashings + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + # Attesting + attestations: List[Attestation, MAX_ATTESTATIONS] + # Enty & exit + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[VoluntaryExit, MAX_VOLUNTARY_EXITS] + # Custody game + custody_chunk_challenges: List[CustodyChunkChallenge, PLACEHOLDER] + custody_bit_challenges: List[CustodyBitChallenge, PLACEHOLDER] + custody_key_reveals: List[CustodyKeyReveal, PLACEHOLDER] + early_derived_secret_reveals: List[EarlyDerivedSecretReveal, PLACEHOLDER] + # Shards + shard_transitions: Vector[ShardTransition, MAX_SHARDS] + # Light clients + light_client_signature_bitfield: Bitlist[LIGHT_CLIENT_COMMITTEE_SIZE] + light_client_signature: BLSSignature +``` ### New extended `BeaconBlock` @@ -189,17 +215,6 @@ class BeaconBlock(phase0.BeaconBlock): parent_root: Hash state_root: Hash body: BeaconBlockBody - shard_transitions: Vector[ShardTransition, MAX_SHARDS] - light_client_signature_bitfield: Bitlist[LIGHT_CLIENT_COMMITTEE_SIZE] - light_client_signature: BLSSignature - - # TODO: older pre-proposal custody field additions, keep this? - custody_chunk_challenges: List[CustodyChunkChallenge, PLACEHOLDER] - custody_bit_challenges: List[CustodyBitChallenge, PLACEHOLDER] - custody_responses: List[CustodyResponse, PLACEHOLDER] - custody_key_reveals: List[CustodyKeyReveal, PLACEHOLDER] - early_derived_secret_reveals: List[EarlyDerivedSecretReveal, PLACEHOLDER] - signature: BLSSignature ``` @@ -240,10 +255,8 @@ class BeaconState(phase0.BeaconState): online_countdown: Bytes[VALIDATOR_REGISTRY_LIMIT] current_light_committee: CompactCommittee next_light_committee: CompactCommittee - - # TODO older pre-proposal custody field additions, keep this? - custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, PLACEHOLDER] - custody_bit_challenge_records: List[CustodyBitChallengeRecord, PLACEHOLDER] + + # TODO: custody game refactor, no challenge-records, immediate processing. custody_challenge_index: uint64 # Future derived secrets already exposed; contains the indices of the exposed validator # at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS @@ -439,11 +452,14 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: process_operations(body.attester_slashings, process_attester_slashing) # New attestation processing - process_attestations(state, block, body.attestations) + process_attestations(state, body, body.attestations) process_operations(body.deposits, process_deposit) process_operations(body.voluntary_exits, process_voluntary_exit) + # See custody game spec. + process_custody_game_operations(state, body) + # TODO process_operations(body.shard_receipt_proofs, process_shard_receipt_proofs) ``` @@ -526,7 +542,7 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr ###### `process_attestations` ```python -def process_attestations(state: BeaconState, block: BeaconBlock, attestations: Sequence[Attestation]) -> None: +def process_attestations(state: BeaconState, block_body: BeaconBlockBody, attestations: Sequence[Attestation]) -> None: pending_attestations = [] # Basic validation for attestation in attestations: @@ -548,25 +564,25 @@ def process_attestations(state: BeaconState, block: BeaconBlock, attestations: S participating_attestations.append(attestation) if attestation.data.shard_transition_root == shard_transition_root: all_participants = all_participants.union(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) - if ( - get_total_balance(state, online_indices.intersection(all_participants)) * 3 >= - get_total_balance(state, online_indices.intersection(this_shard_committee)) * 2 - and success is False - ): - # Attestation <-> shard transition consistency - assert shard_transition_root == hash_tree_root(block.shard_transition) - assert attestation.data.head_shard_root == chunks_to_body_root(block.shard_transition.shard_data_roots[-1]) - # Apply transition - apply_shard_transition(state, shard, block.shard_transition) - # Apply proposer reward and cost - estimated_attester_reward = sum([get_base_reward(state, attester) for attester in all_participants]) - increase_balance(state, proposer, estimated_attester_reward // PROPOSER_REWARD_COEFFICIENT) - for shard_state, slot, length in zip(block.shard_transition.shard_states, offset_slots, block.shard_transition.shard_block_lengths): - decrease_balance(state, get_shard_proposer_index(state, shard, slot), shard_state.gasprice * length) - winners.add((shard, shard_transition_root)) - success = True + if ( + get_total_balance(state, online_indices.intersection(all_participants)) * 3 >= + get_total_balance(state, online_indices.intersection(this_shard_committee)) * 2 + and success is False + ): + # Attestation <-> shard transition consistency + assert shard_transition_root == hash_tree_root(block_body.shard_transition) + assert attestation.data.head_shard_root == chunks_to_body_root(block_body.shard_transition.shard_data_roots[-1]) + # Apply transition + apply_shard_transition(state, shard, block_body.shard_transition) + # Apply proposer reward and cost + estimated_attester_reward = sum([get_base_reward(state, attester) for attester in all_participants]) + increase_balance(state, proposer, estimated_attester_reward // PROPOSER_REWARD_COEFFICIENT) + for shard_state, slot, length in zip(block_body.shard_transition.shard_states, offset_slots, block_body.shard_transition.shard_block_lengths): + decrease_balance(state, get_shard_proposer_index(state, shard, slot), shard_state.gasprice * length) + winners.add((shard, shard_transition_root)) + success = True if not success: - assert block.shard_transitions[shard] == ShardTransition() + assert block_body.shard_transitions[shard] == ShardTransition() for attestation in attestations: pending_attestation = PendingAttestation( aggregation_bits=attestation.aggregation_bits, @@ -586,22 +602,22 @@ def process_attestations(state: BeaconState, block: BeaconBlock, attestations: S #### Shard transition false positives ```python -def verify_shard_transition_false_positives(state: BeaconState, block: BeaconBlock) -> None: +def verify_shard_transition_false_positives(state: BeaconState, block_body: BeaconBlockBody) -> None: # Verify that a `shard_transition` in a block is empty if an attestation was not processed for it for shard in range(MAX_SHARDS): if state.shard_states[shard].slot != state.slot - 1: - assert block.shard_transition[shard] == ShardTransition() + assert block_body.shard_transition[shard] == ShardTransition() ``` #### Light client processing ```python -def process_light_client_signatures(state: BeaconState, block: BeaconBlock) -> None: +def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockBody) -> None: committee = get_light_client_committee(state, get_current_epoch(state)) - assert len(block.light_client_signature_bitfield) == len(committee) + assert len(block_body.light_client_signature_bitfield) == len(committee) total_reward = Gwei(0) signer_keys = [] - for i, bit in enumerate(block.light_client_signature_bitfield): + for i, bit in enumerate(block_body.light_client_signature_bitfield): if bit: signer_keys.append(state.validators[committee[i]].pubkey) increase_balance(state, committee[i], get_base_reward(state, committee[i])) @@ -612,7 +628,7 @@ def process_light_client_signatures(state: BeaconState, block: BeaconBlock) -> N assert bls_verify( pubkey=bls_aggregate_pubkeys(signer_keys), message_hash=get_block_root_at_slot(state, state.slot - 1), - signature=block.light_client_signature, + signature=block_body.light_client_signature, domain=DOMAIN_LIGHT_CLIENT ) ``` @@ -627,26 +643,38 @@ def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) process_rewards_and_penalties(state) process_registry_updates(state) - # TODO process_reveal_deadlines - # TODO process_challenge_deadlines + process_reveal_deadlines(state) + process_challenge_deadlines(state) process_slashings(state) - # TODO update_period_committee process_final_updates(state) - # TODO process_custody_final_updates + process_custody_final_updates(state) + process_online_tracking(state) + process_light_client_committee_updates(state) +``` +#### Online-tracking + +```python +def process_online_tracking(state: BeaconState) -> None: # Slowly remove validators from the "online" set if they do not show up for index in range(len(state.validators)): if state.online_countdown[index] != 0: state.online_countdown[index] = state.online_countdown[index] - 1 - - # Update light client committees - if get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: - state.current_light_committee = state.next_light_committee - new_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD) - state.next_light_committee = committee_to_compact_committee(state, new_committee) # Process pending attestations for pending_attestation in state.current_epoch_attestations + state.previous_epoch_attestations: for index in get_attesting_indices(state, pending_attestation.data, pending_attestation.aggregation_bits): state.online_countdown[index] = ONLINE_PERIOD ``` + +#### Light client committee updates + +```python +def process_light_client_committee_updates(state: BeaconState) -> None: + # Update light client committees + if get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: + state.current_light_committee = state.next_light_committee + new_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD) + state.next_light_committee = committee_to_compact_committee(state, new_committee) +``` + diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index 087dcdbf9..9e880d02b 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -17,7 +17,6 @@ - [Max operations per block](#max-operations-per-block) - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Signature domain types](#signature-domain-types) - - [TODO PLACEHOLDER](#todo-placeholder) - [Data structures](#data-structures) - [Custody objects](#custody-objects) - [`CustodyChunkChallenge`](#custodychunkchallenge) @@ -71,8 +70,6 @@ This document details the beacon chain additions and changes in Phase 1 of Ether - **Custody key**— - **Custody key reveal**— - **Custody key mask**— -- **Custody response**— -- **Custody response deadline**— ## Constants @@ -115,7 +112,6 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` | | `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | | `MAX_CUSTODY_BIT_CHALLENGES` | `2**2` (= 4) | -| `MAX_CUSTODY_RESPONSES` | `2**5` (= 32) | ### Reward and penalty quotients @@ -131,11 +127,6 @@ The following types are defined, mapping into `DomainType` (little endian): | - | - | | `DOMAIN_CUSTODY_BIT_CHALLENGE` | `6` | -### TODO PLACEHOLDER - -| Name | Value | -| - | - | -| `PLACEHOLDER` | `2**32` | ## Data structures @@ -189,19 +180,7 @@ class CustodyBitChallengeRecord(Container): responder_key: BLSSignature ``` -#### `CustodyResponse` - -```python -class CustodyResponse(Container): - challenge_index: uint64 - chunk_index: uint64 - chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] - data_branch: List[Bytes32, CUSTODY_DATA_DEPTH] - chunk_bits_branch: List[Bytes32, CUSTODY_CHUNK_BIT_DEPTH] - chunk_bits_leaf: Bitvector[256] -``` - -### New beacon operations +### New Beacon Chain operations #### `CustodyKeyReveal` @@ -363,16 +342,27 @@ def replace_empty_or_append(list: MutableSequence[Any], new_element: Any) -> int ## Per-block processing -### Operations +### Custody Game Operations -Add the following operations to the per-block processing, in the order given below and after all other operations in Phase 0. +```python +def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) -> None: + assert len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS + assert len(block.body.early_derived_secret_reveals) <= MAX_EARLY_DERIVED_SECRET_REVEALS + assert len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGES + assert len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES + + def process_operations(operations, fn): + for operation in operations: + fn(state, operation) + + process_operations(body.custody_key_reveals, process_custody_key_reveal) + process_operations(body.early_derived_secret_reveals, process_early_derived_secret_reveal) + process_operations(body.custody_chunk_challenges, process_chunk_challenge) + process_operations(body.custody_bit_challenges, process_bit_challenge) +``` #### Custody key reveals -Verify that `len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS`. - -For each `reveal` in `block.body.custody_key_reveals`, run the following function: - ```python def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> None: """ @@ -425,10 +415,6 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> #### Early derived secret reveals -Verify that `len(block.body.early_derived_secret_reveals) <= MAX_EARLY_DERIVED_SECRET_REVEALS`. - -For each `reveal` in `block.body.early_derived_secret_reveals`, run the following function: - ```python def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerivedSecretReveal) -> None: """ @@ -499,10 +485,6 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived #### Chunk challenges -Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALLENGES`. - -For each `challenge` in `block.body.custody_chunk_challenges`, run the following function: - ```python def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None: # Verify the attestation @@ -541,12 +523,36 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge responder.withdrawable_epoch = FAR_FUTURE_EPOCH ``` +TODO: immediate challenge processing, no state records. + +```python +def process_chunk_challenge_response(state: BeaconState, + response: CustodyResponse, + challenge: CustodyChunkChallengeRecord) -> None: + # Verify chunk index + assert response.chunk_index == challenge.chunk_index + # Verify bit challenge data is null + assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == Hash() + # Verify minimum delay + assert get_current_epoch(state) >= challenge.inclusion_epoch + MAX_SEED_LOOKAHEAD + # Verify the chunk matches the crosslink data root + assert is_valid_merkle_branch( + leaf=hash_tree_root(response.chunk), + branch=response.data_branch, + depth=challenge.depth, + index=response.chunk_index, + root=challenge.data_root, + ) + # Clear the challenge + records = state.custody_chunk_challenge_records + records[records.index(challenge)] = CustodyChunkChallengeRecord() + # Reward the proposer + proposer_index = get_beacon_proposer_index(state) + increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) +``` + #### Bit challenges -Verify that `len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGES`. - -For each `challenge` in `block.body.custody_bit_challenges`, run the following function: - ```python def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None: attestation = challenge.attestation @@ -606,52 +612,7 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> responder.withdrawable_epoch = FAR_FUTURE_EPOCH ``` -#### Custody responses - -Verify that `len(block.body.custody_responses) <= MAX_CUSTODY_RESPONSES`. - -For each `response` in `block.body.custody_responses`, run the following function: - -```python -def process_custody_response(state: BeaconState, response: CustodyResponse) -> None: - chunk_challenge = next((record for record in state.custody_chunk_challenge_records - if record.challenge_index == response.challenge_index), None) - if chunk_challenge is not None: - return process_chunk_challenge_response(state, response, chunk_challenge) - - bit_challenge = next((record for record in state.custody_bit_challenge_records - if record.challenge_index == response.challenge_index), None) - if bit_challenge is not None: - return process_bit_challenge_response(state, response, bit_challenge) - - assert False -``` - -```python -def process_chunk_challenge_response(state: BeaconState, - response: CustodyResponse, - challenge: CustodyChunkChallengeRecord) -> None: - # Verify chunk index - assert response.chunk_index == challenge.chunk_index - # Verify bit challenge data is null - assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == Bytes32() - # Verify minimum delay - assert get_current_epoch(state) >= challenge.inclusion_epoch + MAX_SEED_LOOKAHEAD - # Verify the chunk matches the crosslink data root - assert is_valid_merkle_branch( - leaf=hash_tree_root(response.chunk), - branch=response.data_branch, - depth=challenge.depth, - index=response.chunk_index, - root=challenge.data_root, - ) - # Clear the challenge - records = state.custody_chunk_challenge_records - records[records.index(challenge)] = CustodyChunkChallengeRecord() - # Reward the proposer - proposer_index = get_beacon_proposer_index(state) - increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) -``` +TODO: immediate challenge processing, no state records. ```python def process_bit_challenge_response(state: BeaconState, @@ -703,23 +664,6 @@ def process_reveal_deadlines(state: BeaconState) -> None: slash_validator(state, ValidatorIndex(index)) ``` -Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`: - -```python -def process_challenge_deadlines(state: BeaconState) -> None: - for custody_chunk_challenge in state.custody_chunk_challenge_records: - if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: - slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index) - records = state.custody_chunk_challenge - records[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord() - - for custody_bit_challenge in state.custody_bit_challenge_records: - if get_current_epoch(state) > custody_bit_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE: - slash_validator(state, custody_bit_challenge.responder_index, custody_bit_challenge.challenger_index) - records = state.custody_bit_challenge_records - records[records.index(custody_bit_challenge)] = CustodyBitChallengeRecord() -``` - After `process_final_updates(state)`, additional updates are made for the custody game: ```python