From d789f3d32d67ce1cd811dfcc403e5fb76bc256d9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Apr 2020 12:02:56 -0600 Subject: [PATCH 001/236] getting phase 1 val guide in place --- specs/phase1/beacon-chain.md | 34 +++-- specs/phase1/validator.md | 261 +++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 specs/phase1/validator.md diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 9c0cd0a6cb..135e2d3d33 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -30,6 +30,7 @@ - [`ShardTransition`](#shardtransition) - [`CompactCommittee`](#compactcommittee) - [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper) + - [`LightClientVote`](#lightclientvote) - [Helper functions](#helper-functions) - [Misc](#misc-1) - [`get_previous_slot`](#get_previous_slot) @@ -211,7 +212,7 @@ class BeaconBlockBody(Container): # Shards shard_transitions: Vector[ShardTransition, MAX_SHARDS] # Light clients - light_client_signature_bitfield: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] + light_client_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] light_client_signature: BLSSignature ``` @@ -355,6 +356,16 @@ class AttestationCustodyBitWrapper(Container): bit: boolean ``` +### `LightClientVote` + +```python +class LightClientVote(Container): + slot: Slot + block_root: Root + aggregation_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] + signature: BLSSignature +``` + ## Helper functions ### Misc @@ -577,7 +588,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_randao(state, block.body) process_eth1_data(state, block.body) verify_shard_transition_false_positives(state, block.body) - process_light_client_signatures(state, block.body) + process_light_client_aggregate(state, block.body) process_operations(state, block.body) ``` @@ -862,21 +873,24 @@ def verify_shard_transition_false_positives(state: BeaconState, block_body: Beac #### Light client processing ```python -def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockBody) -> None: +def process_light_client_aggregate(state: BeaconState, block_body: BeaconBlockBody) -> None: committee = get_light_client_committee(state, get_current_epoch(state)) + previous_slot = get_previous_slot(state.slot) + previous_block_root = get_block_root_at_slot(state, previous_slot) + total_reward = Gwei(0) signer_pubkeys = [] for bit_index, participant_index in enumerate(committee): - if block_body.light_client_signature_bitfield[bit_index]: + if block_body.light_client_bits[bit_index]: signer_pubkeys.append(state.validators[participant_index].pubkey) - increase_balance(state, participant_index, get_base_reward(state, participant_index)) - total_reward += get_base_reward(state, participant_index) + if not state.validators[participant_index].slashed: + increase_balance(state, participant_index, get_base_reward(state, participant_index)) + total_reward += get_base_reward(state, participant_index) increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) - - slot = get_previous_slot(state.slot) - signing_root = compute_signing_root(get_block_root_at_slot(state, slot), - get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot))) + + signing_root = compute_signing_root(previous_block_root, + get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(previous_slot))) assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) ``` diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md new file mode 100644 index 0000000000..600f05c175 --- /dev/null +++ b/specs/phase1/validator.md @@ -0,0 +1,261 @@ +# Ethereum 2.0 Phase 0 -- Honest Validator + +**Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 1](./), which describes the expected actions of a "validator" participating in the Ethereum 2.0 Phase 1 protocol. + +## Table of contents + + + + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Constants](#constants) +- [Becoming a validator](#becoming-a-validator) +- [Beacon chain validator assignments](#beacon-chain-validator-assignments) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [Custody slashings](#custody-slashings) + - [Custody key reveals](#custody-key-reveals) + - [Early derived secret reveals](#early-derived-secret-reveals) + - [Shard transitions](#shard-transitions) + - [Light client fields](#light-client-fields) + - [Packaging into a `SignedBeaconBlock`](#packaging-into-a-signedbeaconblock) + - [Attesting](#attesting) + - [`FullAttestationData`](#fullattestationdata) + - [`FullAttestation`](#fullattestation) + - [Timing](#timing) + - [Attestation data](#attestation-data) + - [Head shard root](#head-shard-root) + - [Shard transition](#shard-transition) + - [Construct attestation](#construct-attestation) + - [Custody bits blocks](#custody-bits-blocks) + - [Signature](#signature) + + + + +## Introduction + +This document represents the expected behavior of an "honest validator" with respect to Phase 1 of the Ethereum 2.0 protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope. + +A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof-of-work networks in which miners provide collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol. + +## Prerequisites + +This document is an extension of the [Phase 0 -- Validator](../phase0/validator.md). All behaviors and definitions defined in the Phase 0 doc carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the [Phase 1 -- The Beacon Chain](./beacon-chain.md) and [Phase 1 -- Custody Game](./custody-game.md) docs are requisite for this document and used throughout. Please see the Phase 1 docs before continuing and use as a reference throughout. + +## Constants + +See constants from [Phase 0 validator guide](../phase0/validator.md#constants). + +## Becoming a validator + +Becoming a validator in Phase 1 is unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#becoming-a-validator) for details. + +## Beacon chain validator assignments + +Beacon chain validator assignments to beacon committees and beacon block proposal are unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#validator-assignments) for details. + +## Beacon chain responsibilities + +A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch. + +These responsibilities are largely unchanged from Phase 0, but utilize the updated `SignedBeaconBlock`, `BeaconBlock`, `BeaconBlockBody`, `Attestation`, and `AttestationData` definitions found in Phase 1. Below notes only the additional and modified behavior with respect to Phase 0. + +### Block proposal + +#### Preparing for a `BeaconBlock` + +`slot`, `proposer_index`, `parent_root` fields are unchanged. + +#### Constructing the `BeaconBlockBody` + +`randao_reveal`, `eth1_data`, and `graffiti` are unchanged. + +`proposer_slashings`, `deposits`, and `voluntary_exits` are unchanged. + +`attester_slashings` and `attestations` operate exactly as in Phase 0, but with new definitations of `AttesterSlashing` and `Attestation`, along with modified validation conditions found in `process_attester_slashing` and `process_attestation`. + +##### Custody slashings + +Up to `MAX_CUSTODY_SLASHINGS`, [`CustodySlashing`](./custody-game.md#custodyslashing) objects can be included in the `block`. The custody slashings must satisfy the verification conditions found in [custody slashings processing](./custody-game.md#custody-slashings). The validator receives a small "whistleblower" reward for each custody slashing included (THIS IS NOT CURRENTLY THE CASE BUT PROBABLY SHOULD BE). + +##### Custody key reveals + +Up to `MAX_CUSTODY_KEY_REVEALS`, [`CustodyKeyReveal`](./custody-game.md#custodykeyreveal) objects can be included in the `block`. The custody key reveals must satisfy the verification conditions found in [custody key reveal processing](./custody-game.md#custody-key-reveals). The validator receives a small reward for each custody key reveal included. + +##### Early derived secret reveals + +Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./custody-game.md#earlyderivedsecretreveal) objects can be included in the `block`. The early derived secret reveals must satisfy the verification conditions found in [early derived secret reveal processing](./custody-game.md#custody-key-reveals). The validator receives a small "whistleblower" reward for each early derived secrete reveal included. + +##### Shard transitions + +Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot. + +Specifically: +* Call `shards, winning_roots = get_successful_shard_transitions(state, block.slot, attestations)` +* Let `full_transitions` be a dictionary mapping from the `shard_transition_root`s found in `attestations` to the corresponding full `ShardTransition` +* Then for each `shard` and `winning_root` in `zip(shards, winning_roots)` set `shard_transitions[shard] = full_transitions[winning_root]` + +```python +def get_successful_shard_transitions(state: BeaconState, + slot: Slot, + attestations: Attestation) -> Tuple[Sequence[Shard], Sequence[Root]]: + shards = [] + winning_roots = [] + committee_count = get_committee_count_at_slot(state, slot) + for committee_index in map(CommitteeIndex, range(committee_count)): + shard = compute_shard_from_committee_index(state, committee_index, slot) + # All attestations in the block for this committee/shard and current slot + shard_attestations = [ + attestation for attestation in attestations + if attestation.data.index == committee_index and attestation.data.slot == slot + ] + committee = get_beacon_committee(state, state.slot, committee_index) + + # Loop over all shard transition roots, looking for a winning root + shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) + for shard_transition_root in sorted(shard_transition_roots): + transition_attestations = [a for a in attestations if a.data.shard_transition_root == shard_transition_root] + transition_participants: Set[ValidatorIndex] = set() + for attestation in transition_attestations: + participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + transition_participants = transition_participants.union(participants) + + enough_online_stake = ( + get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >= + get_total_balance(state, online_indices.intersection(committee)) * 2 + ) + if enough_online_stake: + shards.append(shard) + transitions.append(shard_transition_root) + break + + return shards, winning_roots +``` + +##### Light client fields + +First retrieve `best_aggregate` from `get_best_light_client_aggregate` where `aggregates` is a list of valid aggregated `LightClientVote`s for the previous slot. + +Then: +* Set `light_client_bits = best_aggregate.aggregation_bits` +* Set `light_client_signature = best_aggregate.signature` + +```python +def select_best_light_client_aggregate(block: BeaconBlock, + aggregates: Sequence[LightClientVote]) -> LightClientVote: + viable_aggregates = [ + aggregate in aggregates + if aggregate.slot == get_previous_slot(block.slot) and aggregate.beacon_block_root == block.parent_root + ] + + return max( + viable_aggregates, + key=lambda a: len([_ for i in a.aggregation_bits if i == 1]), + default=LightClientVote(), + ) +``` + +#### Packaging into a `SignedBeaconBlock` + +Packaging into a `SignedBeaconBlock` is unchanged from Phase 0. + +### Attesting + +A validator is expected to create, sign, and broadcast an attestation during each epoch. + +Assignments and the core of this duty are unchanged from Phase 0. There are a few additional fields related to the assigned shard chain and custody bit. + +The `Attestation` and `AttestationData` defined in the [Phase 1 Beacon Chain spec]() utilizes `shard_transition_root: Root` rather than a full `ShardTransition`. For the purposes of the validator and p2p layer, a modified `FullAttestationData` and containing `FullAttestation` are used to send the accompanying `ShardTransition` in its entirety. Note that due to the properties of SSZ `hash_tree_root`, the root and signatures of `AttestationData` and `FullAttestationData` are equivalent. + +#### `FullAttestationData` + +```python +class FullAttestationData(Container): + slot: Slot + index: CommitteeIndex + # LMD GHOST vote + beacon_block_root: Root + # FFG vote + source: Checkpoint + target: Checkpoint + # Current-slot shard block root + head_shard_root: Root + # Full shard transition + shard_transition: ShardTransition +``` + +#### `FullAttestation` + +```python +class FullAttestation(Container): + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] + data: FullAttestationData + custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION] + signature: BLSSignature +``` + +#### Timing + +Note the timing of when to create/broadcast is altered from Phase 1. + +A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid `BeaconBlock` from the expected beacon block proposer and a valid `ShardBlock` for the expected shard block porposer for the assigned `slot` or (b) one-half of the `slot` has transpired (`SECONDS_PER_SLOT / 2` seconds after the start of `slot`) -- whichever comes _first_. + +#### Attestation data + +`attestation_data` is constructed in the same manner as Phase 0 but uses `FullAttestationData` with the addition of two fields -- `head_shard_root` and `shard_transition`. + +- Let `head_block` be the result of running the fork choice during the assigned slot. +- Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. +- Let `head_shard_block` be the result of running the fork choice on the assigned shard chain during the assigned slot. + +##### Head shard root + +Set `attestation_data.head_shard_root = hash_tree_root(head_shard_block)`. + +##### Shard transition + +Set `shard_transition` to the value returned by `get_shard_transition()`. + +```python +def get_shard_transition(state: BeaconState, shard: Shard, shard_blocks: Sequence[ShardBlock]) + latest_shard_slot = get_latest_slot_for_shard(state, shard) + offset_slots = [Slot(latest_shard_slot + x) for x in SHARD_BLOCK_OFFSETS if latest_shard_slot + x <= state.slot] + return ShardTransition() +``` + +#### Construct attestation + +Next, the validator creates `attestation`, a `FullAttestation` as defined above. + +`attestation.data` and `attestation.aggregation_bits` are unchanged from Phase 0. + +##### Custody bits blocks + +- Let `attestation.custody_bits_blocks` be a the value returned by `get_custody_bits_blocks()` + +```python +def get_custody_bits_blocks() -> List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]: + pass +``` + +##### Signature + +Set `attestation.signature = attestation_signature` where `attestation_signature` is obtained from: + +```python +def get_attestation_signature(state: BeaconState, + attestation_data: AttestationData, + custody_bits_blocks, + privkey: int) -> List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]: + pass +``` + + From 6067c511c5043aa642d175d84b46d10d3e26f55b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Apr 2020 16:48:02 -0600 Subject: [PATCH 002/236] add light client to phase 1 validator --- specs/phase1/beacon-chain.md | 25 ++--- specs/phase1/validator.md | 202 +++++++++++++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 21 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 135e2d3d33..bca952a92d 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -12,6 +12,7 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) + - [Domain types](#domain-types) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) @@ -30,7 +31,6 @@ - [`ShardTransition`](#shardtransition) - [`CompactCommittee`](#compactcommittee) - [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper) - - [`LightClientVote`](#lightclientvote) - [Helper functions](#helper-functions) - [Misc](#misc-1) - [`get_previous_slot`](#get_previous_slot) @@ -105,9 +105,16 @@ Configuration is not namespaced. Instead it is strictly an extension; | `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | | | `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | | | `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | | -| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | -| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | -| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | + +### Domain types + +| Name | Value | +| - | - | +| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | +| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | +| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | +| `DOMAIN_LIGHT_SELECTION_PROOF` | `DomainType('0x83000000')` | +| `DOMAIN_LIGHT_AGGREGATE_AND_PROOF` | `DomainType('0x84000000')` | ## Updated containers @@ -356,16 +363,6 @@ class AttestationCustodyBitWrapper(Container): bit: boolean ``` -### `LightClientVote` - -```python -class LightClientVote(Container): - slot: Slot - block_root: Root - aggregation_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] - signature: BLSSignature -``` - ## Helper functions ### Misc diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 600f05c175..acd98ee5d3 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -12,6 +12,7 @@ - [Introduction](#introduction) - [Prerequisites](#prerequisites) - [Constants](#constants) + - [Misc](#misc) - [Becoming a validator](#becoming-a-validator) - [Beacon chain validator assignments](#beacon-chain-validator-assignments) - [Beacon chain responsibilities](#beacon-chain-responsibilities) @@ -34,6 +35,20 @@ - [Construct attestation](#construct-attestation) - [Custody bits blocks](#custody-bits-blocks) - [Signature](#signature) + - [Light client committee](#light-client-committee) + - [Preparation](#preparation) + - [Light clent vote](#light-clent-vote) + - [Light client vote data](#light-client-vote-data) + - [`LightClientVoteData`](#lightclientvotedata) + - [Construct vote](#construct-vote) + - [`LightClientVote`](#lightclientvote) + - [Broadcast](#broadcast) + - [Light client vote aggregation](#light-client-vote-aggregation) + - [Aggregation selection](#aggregation-selection) + - [Construct aggregate](#construct-aggregate) + - [Broadcast aggregate](#broadcast-aggregate) + - [`LightAggregateAndProof`](#lightaggregateandproof) + - [`SignedLightAggregateAndProof`](#signedlightaggregateandproof) @@ -54,6 +69,12 @@ All terminology, constants, functions, and protocol mechanics defined in the [Ph See constants from [Phase 0 validator guide](../phase0/validator.md#constants). +### Misc + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT` | `2**2` (= 8) | validators | | + ## Becoming a validator Becoming a validator in Phase 1 is unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#becoming-a-validator) for details. @@ -68,6 +89,8 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo These responsibilities are largely unchanged from Phase 0, but utilize the updated `SignedBeaconBlock`, `BeaconBlock`, `BeaconBlockBody`, `Attestation`, and `AttestationData` definitions found in Phase 1. Below notes only the additional and modified behavior with respect to Phase 0. +Phase 1 adds light client committees and associated responsibilities, discussed [below](#light-client-committee). + ### Block proposal #### Preparing for a `BeaconBlock` @@ -120,9 +143,12 @@ def get_successful_shard_transitions(state: BeaconState, committee = get_beacon_committee(state, state.slot, committee_index) # Loop over all shard transition roots, looking for a winning root - shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) + shard_transition_roots = set([a.data.shard_transition_root for a in shard_attestations]) for shard_transition_root in sorted(shard_transition_roots): - transition_attestations = [a for a in attestations if a.data.shard_transition_root == shard_transition_root] + transition_attestations = [ + a for a in shard_attestations + if a.data.shard_transition_root == shard_transition_root + ] transition_participants: Set[ValidatorIndex] = set() for attestation in transition_attestations: participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) @@ -149,10 +175,10 @@ Then: * Set `light_client_signature = best_aggregate.signature` ```python -def select_best_light_client_aggregate(block: BeaconBlock, - aggregates: Sequence[LightClientVote]) -> LightClientVote: +def get_best_light_client_aggregate(block: BeaconBlock, + aggregates: Sequence[LightClientVote]) -> LightClientVote: viable_aggregates = [ - aggregate in aggregates + aggregate for aggregate in aggregates if aggregate.slot == get_previous_slot(block.slot) and aggregate.beacon_block_root == block.parent_root ] @@ -225,7 +251,7 @@ Set `attestation_data.head_shard_root = hash_tree_root(head_shard_block)`. Set `shard_transition` to the value returned by `get_shard_transition()`. ```python -def get_shard_transition(state: BeaconState, shard: Shard, shard_blocks: Sequence[ShardBlock]) +def get_shard_transition(state: BeaconState, shard: Shard, shard_blocks: Sequence[ShardBlock]) -> ShardTransition: latest_shard_slot = get_latest_slot_for_shard(state, shard) offset_slots = [Slot(latest_shard_slot + x) for x in SHARD_BLOCK_OFFSETS if latest_shard_slot + x <= state.slot] return ShardTransition() @@ -254,8 +280,170 @@ Set `attestation.signature = attestation_signature` where `attestation_signature def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, custody_bits_blocks, - privkey: int) -> List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]: + privkey: int + ) -> List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]: pass ``` +### Light client committee + +In addition to the core beacon chain responsibilities, Phase 1 adds an additional role -- the Light Client Committee -- to aid in light client functionality. + +Validators serve on the light client committee for `LIGHT_CLIENT_COMMITTEE_PERIOD` epochs and the assignment to be on a committee is known `LIGHT_CLIENT_COMMITTEE_PERIOD` epochs in advance. + +#### Preparation + +When `get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == LIGHT_CLIENT_COMMITTEE_PERIOD - LIGHT_CLIENT_PREPARATION_EPOCHS` each validator must check if they are in the next period light client committee by calling `is_in_next_light_client_committee()`. + +If the validator is in the next light client committee, they must join the `light_client_votes` pubsub topic to begin duties at the start of the next period. + +```python +def is_in_next_light_client_committee(state: BeaconState, index: ValidatorIndex) -> boolean: + period_start_epoch = get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD % get_current_epoch(state) + next_committee = get_light_client_committee(state, period_start_epoch) + return index in next_committee +``` + +#### Light clent vote + +During a period of epochs that the validator is a part of the light client committee (`validator_index in get_light_client_committee(state, epoch)`), the validator creates and broadcasts a `LightClientVote` at each slot. + +A validator should create and broadcast the `light_client_vote` to the `light_client_votes` pubsub topic when either (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) two-thirds of the `slot` have transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_. + +- Let `light_client_committee = get_light_client_committee(state, compute_epoch_at_slot(slot))` + +##### Light client vote data + +First the validator constructs `light_client_vote_data`, a [`LightClientVoteData`](#lightclientvotedata) object. + +* Let `head_block` be the result of running the fork choice during the assigned slot. +* Set `light_client_vote.slot = slot`. +* Set `light_client_vote.beacon_block_root = hash_tree_root(head_block)`. + +###### `LightClientVoteData` + +```python +class LightClientVoteData(Container): + slot: Slot + beacon_block_root: Root +``` + +##### Construct vote + +Then the validator constructs `light_client_vote`, a [`LightClientVote`](#lightclientvote) object. + +* Set `light_client_vote.data = light_client_vote_data`. +* Set `light_client_vote.aggregation_bits` to be a `Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE]`, where the bit of the index of the validator in the `light_client_committee` is set to `0b1` and all other bits are are set to `0b0`. +* Set `light_client_vote.signature = vote_signature` where `vote_signature` is obtained from: + +```python +def get_light_client_vote_signature(state: BeaconState, + light_client_vote_data: LightClientVoteData, + privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(light_client_vote_data.slot)) + signing_root = compute_signing_root(light_client_vote_data, domain) + return bls.Sign(privkey, signing_root) +``` + +###### `LightClientVote` + +```python +class LightClientVote(Container): + data: LightClientVoteData + aggregation_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE] + signature: BLSSignature +``` + +##### Broadcast + +Finally, the validator broadcasts `light_client_vote` to the `light_client_votes` pubsub topic. + +#### Light client vote aggregation + +Some validators in the light client committee are selected to locally aggregate light client votes with a similar `light_client_vote_data` to their constructed `light_client_vote` for the assigned `slot`. + +#### Aggregation selection + +A validator is selected to aggregate based upon the return value of `is_light_client_aggregator()`. + +```python +def get_light_client_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: + domain = get_domain(state, DOMAIN_LIGHT_SELECTION_PROOF, compute_epoch_at_slot(slot)) + signing_root = compute_signing_root(slot, domain) + return bls.Sign(privkey, signing_root) +``` + +```python +def is_light_client_aggregator(state: BeaconState, slot: Slot, slot_signature: BLSSignature) -> bool: + committee = get_light_client_committee(state, compute_epoch_at_slot(slot)) + modulo = max(1, len(committee) // TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT) + return bytes_to_int(hash(slot_signature)[0:8]) % modulo == 0 +``` + +#### Construct aggregate + +If the validator is selected to aggregate (`is_light_client_aggregator()`), they construct an aggregate light client vote via the following. + +Collect `light_client_votes` seen via gossip during the `slot` that have an equivalent `light_client_vote_data` to that constructed by the validator, and create a `aggregate_light_client_vote: LightClientVote` with the following fields. + +* Set `aggregate_light_client_vote.data = light_client_vote_data` where `light_client_vote_data` is the `LightClientVoteData` object that is the same for each individual light client vote being aggregated. +* Set `aggregate_light_client_vote.aggregation_bits` to be a `Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE]`, where each bit set from each individual light client vote is set to `0b1`. +* Set `aggregate_light_client_vote.signature = aggregate_light_client_signature` where `aggregate_light_client_signature` is obtained from `get_aggregate_light_client_signature`. + +```python +def get_aggregate_light_client_signature(light_client_votes: Sequence[LightClientVote]) -> BLSSignature: + signatures = [light_client_vote.signature for light_client_vote in light_client_votes] + return bls.Aggregate(signatures) +``` + +#### Broadcast aggregate + +If the validator is selected to aggregate (`is_light_client_aggregator`), then they broadcast their best aggregate light client vote as a `SignedLightAggregateAndProof` to the global aggregate light client vote channel (`aggregate_light_client_votes`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`. + +Selection proofs are provided in `LightAggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator. + +`LightAggregateAndProof` messages are signed by the aggregator and broadcast inside of `SignedLightAggregateAndProof` objects to prevent a class of DoS attacks and message forgeries. + +First, `light_aggregate_and_proof = get_light_aggregate_and_proof(state, validator_index, aggregate_light_client_vote, privkey)` is constructed. + +```python +def get_light_aggregate_and_proof(state: BeaconState, + aggregator_index: ValidatorIndex, + aggregate: Attestation, + privkey: int) -> LightAggregateAndProof: + return LightAggregateAndProof( + aggregator_index=aggregator_index, + aggregate=aggregate, + selection_proof=get_light_client_slot_signature(state, aggregate.data.slot, privkey), + ) +``` + +Then `signed_light_aggregate_and_proof = SignedLightAggregateAndProof(message=light_aggregate_and_proof, signature=signature)` is constructed and broadast. Where `signature` is obtained from: + +```python +def get_light_aggregate_and_proof_signature(state: BeaconState, + aggregate_and_proof: LightAggregateAndProof, + privkey: int) -> BLSSignature: + aggregate = aggregate_and_proof.aggregate + domain = get_domain(state, DOMAIN_CLIENT_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + signing_root = compute_signing_root(aggregate_and_proof, domain) + return bls.Sign(privkey, signing_root) +``` + +##### `LightAggregateAndProof` + +```python +class LightAggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature +``` + +##### `SignedLightAggregateAndProof` + +```python +class SignedLightAggregateAndProof(Container): + message: LightAggregateAndProof + signature: BLSSignature +``` From d61b2991a0955b8934e9e4a780ed880a1b11b3af Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 2 Apr 2020 16:58:39 -0600 Subject: [PATCH 003/236] fix lint --- setup.py | 4 +++- specs/phase1/validator.md | 28 ++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index 911eb65b0e..28179bfcee 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,7 @@ def get_spec(file_name: str) -> SpecObject: PHASE1_IMPORTS = '''from eth2spec.phase0 import spec as phase0 from eth2spec.config.config_util import apply_constants_config from typing import ( - Any, Dict, Set, Sequence, NewType, Tuple, TypeVar, Callable + Any, Dict, Set, Sequence, NewType, Tuple, Optional, TypeVar, Callable ) from dataclasses import ( @@ -373,11 +373,13 @@ def finalize_options(self): self.md_doc_paths = """ specs/phase0/beacon-chain.md specs/phase0/fork-choice.md + specs/phase0/validator.md specs/phase1/custody-game.md specs/phase1/beacon-chain.md specs/phase1/fraud-proofs.md specs/phase1/fork-choice.md specs/phase1/phase1-fork.md + specs/phase1/validator.md """ else: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index acd98ee5d3..fd8787adf9 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -132,6 +132,7 @@ def get_successful_shard_transitions(state: BeaconState, attestations: Attestation) -> Tuple[Sequence[Shard], Sequence[Root]]: shards = [] winning_roots = [] + online_indices = get_online_validator_indices(state) committee_count = get_committee_count_at_slot(state, slot) for committee_index in map(CommitteeIndex, range(committee_count)): shard = compute_shard_from_committee_index(state, committee_index, slot) @@ -160,7 +161,7 @@ def get_successful_shard_transitions(state: BeaconState, ) if enough_online_stake: shards.append(shard) - transitions.append(shard_transition_root) + winning_roots.append(shard_transition_root) break return shards, winning_roots @@ -184,7 +185,7 @@ def get_best_light_client_aggregate(block: BeaconBlock, return max( viable_aggregates, - key=lambda a: len([_ for i in a.aggregation_bits if i == 1]), + key=lambda a: len([i for i in a.aggregation_bits if i == 1]), default=LightClientVote(), ) ``` @@ -251,9 +252,13 @@ Set `attestation_data.head_shard_root = hash_tree_root(head_shard_block)`. Set `shard_transition` to the value returned by `get_shard_transition()`. ```python -def get_shard_transition(state: BeaconState, shard: Shard, shard_blocks: Sequence[ShardBlock]) -> ShardTransition: +def get_shard_transition(state: BeaconState, + shard: Shard, + shard_blocks: Sequence[ShardBlockWrapper]) -> ShardTransition: + """ latest_shard_slot = get_latest_slot_for_shard(state, shard) offset_slots = [Slot(latest_shard_slot + x) for x in SHARD_BLOCK_OFFSETS if latest_shard_slot + x <= state.slot] + """ return ShardTransition() ``` @@ -279,9 +284,8 @@ Set `attestation.signature = attestation_signature` where `attestation_signature ```python def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, - custody_bits_blocks, - privkey: int - ) -> List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]: + cb_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION], + privkey: int) -> BLSSignature: pass ``` @@ -408,9 +412,9 @@ First, `light_aggregate_and_proof = get_light_aggregate_and_proof(state, validat ```python def get_light_aggregate_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - aggregate: Attestation, - privkey: int) -> LightAggregateAndProof: + aggregator_index: ValidatorIndex, + aggregate: Attestation, + privkey: int) -> LightAggregateAndProof: return LightAggregateAndProof( aggregator_index=aggregator_index, aggregate=aggregate, @@ -422,10 +426,10 @@ Then `signed_light_aggregate_and_proof = SignedLightAggregateAndProof(message=li ```python def get_light_aggregate_and_proof_signature(state: BeaconState, - aggregate_and_proof: LightAggregateAndProof, - privkey: int) -> BLSSignature: + aggregate_and_proof: LightAggregateAndProof, + privkey: int) -> BLSSignature: aggregate = aggregate_and_proof.aggregate - domain = get_domain(state, DOMAIN_CLIENT_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + domain = get_domain(state, DOMAIN_LIGHT_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) signing_root = compute_signing_root(aggregate_and_proof, domain) return bls.Sign(privkey, signing_root) ``` From f135eff021483a832942373e7af0d82ae99aa58d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 3 Apr 2020 08:33:32 -0600 Subject: [PATCH 004/236] add lookahed for shard subnets for beacon committee in validator guide --- specs/phase1/validator.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index fd8787adf9..a26987a342 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -15,6 +15,7 @@ - [Misc](#misc) - [Becoming a validator](#becoming-a-validator) - [Beacon chain validator assignments](#beacon-chain-validator-assignments) + - [Lookahead](#lookahead) - [Beacon chain responsibilities](#beacon-chain-responsibilities) - [Block proposal](#block-proposal) - [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock) @@ -83,6 +84,14 @@ Becoming a validator in Phase 1 is unchanged from Phase 0. See the [Phase 0 vali Beacon chain validator assignments to beacon committees and beacon block proposal are unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#validator-assignments) for details. +### Lookahead + +Lookahead for beacon committee assignments operates in the same manner as Phase 0, but committee members must join a shard block pubsub topic in addition to the committee attestation topic.o + +Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a validator should: +* Let `shard = compute_shard_from_committee_index(committe_index)` +* Subscribe to the pubsub topic `shard_{shard}_shard_block` (attestation subnet peers should have this topic available). + ## Beacon chain responsibilities A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch. From ca6af0c2e9bfba1667ea7b6a67a03144be7aa23b Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 14:39:00 +0100 Subject: [PATCH 005/236] 256-bit custody atoms for better alignment with rest of the spec and greater efficiency --- specs/phase1/custody-game.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index eb243f8fb8..b4033ea4de 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -49,8 +49,9 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | Unit | | - | - | - | -| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` | - | -| `BYTES_PER_CUSTODY_ATOM` | `48` | bytes | +| `CUSTODY_PRIME` | `2 ** 256 - 189` | - | +| `CUSTODY_SECRETS` | `3` | - | +| `BYTES_PER_CUSTODY_ATOM` | `32` | bytes | ## Configuration @@ -175,7 +176,7 @@ def legendre_bit(a: int, q: int) -> int: return 0 ``` -### `custody_atoms` +### `get_custody_atoms` Given one set of data, return the custody atoms: each atom will be combined with one legendre bit. @@ -186,16 +187,28 @@ def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM)] ``` +### `get_custody_secrets` + +Extract the custody secrets from the signature + +```python +def get_custody_secrets(key: BLSSignature): + full_G2_element = bls.signature_to_G2(key) + signature = full_G2_element[0].coeffs + signature_bytes = sum(x.to_bytes(48, "little") for x in signature) + secrets = [int.from_bytes(x[i:i+BYTES_PER_CUSTODY_ATOM]) for i in range(0, len(signature_bytes), 32)] + return secrets +``` + ### `compute_custody_bit` ```python def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: - full_G2_element = bls.signature_to_G2(key) - s = full_G2_element[0].coeffs + secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) - a = sum(s[i % 2]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms) + s[n % 2]**n) - return legendre_bit(a, BLS12_381_Q) + uhf = sum(secrets[i % CUSTORY_SECRETS]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTORY_SECRETS]**n + return legendre_bit(uhf + secrets[0], BLS12_381_Q) ``` ### `get_randao_epoch_for_custody_period` From bf34fdf0239541b713afeabe527c557d54c7c1b8 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 15:10:09 +0100 Subject: [PATCH 006/236] Fix ToC --- specs/phase1/custody-game.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index b4033ea4de..3709725028 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -24,7 +24,8 @@ - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`legendre_bit`](#legendre_bit) - - [`custody_atoms`](#custody_atoms) + - [`get_custody_secrets`](#get_custody_secrets) + - [`get_custody_atoms`](#get_custody_atoms) - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) From c3c24b4fc4a1592a9dc6d42fb57694aae59ee383 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 15:35:11 +0100 Subject: [PATCH 007/236] Fix lint --- specs/phase1/custody-game.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 3709725028..c1ed243837 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -193,11 +193,12 @@ def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: Extract the custody secrets from the signature ```python -def get_custody_secrets(key: BLSSignature): +def get_custody_secrets(key: BLSSignature) -> Sequence[int]: full_G2_element = bls.signature_to_G2(key) signature = full_G2_element[0].coeffs - signature_bytes = sum(x.to_bytes(48, "little") for x in signature) - secrets = [int.from_bytes(x[i:i+BYTES_PER_CUSTODY_ATOM]) for i in range(0, len(signature_bytes), 32)] + signature_bytes = b"".join(x.to_bytes(48, "little") for x in signature) + secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") + for i in range(0, len(signature_bytes), 32)] return secrets ``` @@ -208,8 +209,9 @@ def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) - uhf = sum(secrets[i % CUSTORY_SECRETS]**i * int.from_bytes(atom, "little") for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTORY_SECRETS]**n - return legendre_bit(uhf + secrets[0], BLS12_381_Q) + uhf = (sum(secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME + for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTODY_SECRETS]**n) % CUSTODY_PRIME + return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) ``` ### `get_randao_epoch_for_custody_period` From 907c56dabdeae9020decb76437f7d6b772838078 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sun, 5 Apr 2020 15:47:59 +0100 Subject: [PATCH 008/236] Fix ToC --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index c1ed243837..6315955f58 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -24,8 +24,8 @@ - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`legendre_bit`](#legendre_bit) - - [`get_custody_secrets`](#get_custody_secrets) - [`get_custody_atoms`](#get_custody_atoms) + - [`get_custody_secrets`](#get_custody_secrets) - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) From ab2ee0e2c2898ccb8398d555e64a8a6d34ebbeec Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 24 Apr 2020 17:06:27 +0100 Subject: [PATCH 009/236] Restoring chunk challenges and testing --- Makefile | 2 +- configs/minimal.yaml | 4 +- setup.py | 2 +- specs/phase1/beacon-chain.md | 95 +++++ specs/phase1/custody-game.md | 165 ++++++--- .../eth2spec/test/helpers/attestations.py | 15 +- .../pyspec/eth2spec/test/helpers/custody.py | 72 ++-- .../test/helpers/phase1/attestations.py | 6 +- .../test_process_chunk_challenge.py | 238 ++++++++++++ .../test_process_custody_slashing.py | 349 ++++++++++++++++++ .../test_process_final_custody_updates.py | 319 ++++++++++++++++ .../test_process_reveal_deadlines.py | 46 +++ 12 files changed, 1214 insertions(+), 99 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py diff --git a/Makefile b/Makefile index e8f3d21bc5..8cd7daf58c 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ pyspec: # installs the packages to run pyspec tests install_test: - python3 -m venv venv; . venv/bin/activate; pip3 install .[test] .[lint] + python3.8 -m venv venv; . venv/bin/activate; pip3 install .[lint]; pip3 install -e .[test] test: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 9daf428b4a..256c2b3fa0 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -205,9 +205,9 @@ RANDAO_PENALTY_EPOCHS: 2 # [customized] quicker for testing EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 # 2**11 (= 2,048) epochs -EPOCHS_PER_CUSTODY_PERIOD: 2048 +EPOCHS_PER_CUSTODY_PERIOD: 8 # 2**11 (= 2,048) epochs -CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 +CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 # 2**7 (= 128) epochs MAX_REVEAL_LATENESS_DECREMENT: 128 diff --git a/setup.py b/setup.py index 911eb65b0e..91c29a6ccf 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ def get_spec(file_name: str) -> SpecObject: from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( View, boolean, Container, List, Vector, uint64, uint8, bit, - ByteList, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, + ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils import bls diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 596b3818f8..18c5f347fd 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -109,6 +109,99 @@ Configuration is not namespaced. Instead it is strictly an extension; | `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | | `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | | `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | +| `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes | +| `MAX_CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - | + + + +## New containers + +### `CustodyChunkChallenge` + +```python +class CustodyChunkChallenge(Container): + responder_index: ValidatorIndex + shard_transition: ShardTransition + attestation: Attestation + data_index: uint64 + chunk_index: uint64 +``` + +### `CustodyChunkChallengeRecord` + +```python +class CustodyChunkChallengeRecord(Container): + challenge_index: uint64 + challenger_index: ValidatorIndex + responder_index: ValidatorIndex + inclusion_epoch: Epoch + data_root: Root + depth: uint64 + chunk_index: uint64 +``` + +#### `CustodyChunkResponse` + +```python +class CustodyChunkResponse(Container): + challenge_index: uint64 + chunk_index: uint64 + chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] + branch: List[Root, MAX_CUSTODY_RESPONSE_DEPTH] +``` + +#### `CustodySlashing` + +```python +class CustodySlashing(Container): + # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. + # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. + data_index: uint64 + malefactor_index: ValidatorIndex + malefactor_secret: BLSSignature + whistleblower_index: ValidatorIndex + shard_transition: ShardTransition + attestation: Attestation + data: ByteList[MAX_SHARD_BLOCK_SIZE] +``` + +#### `SignedCustodySlashing` + +```python +class SignedCustodySlashing(Container): + message: CustodySlashing + signature: BLSSignature +``` + + +#### `CustodyKeyReveal` + +```python +class CustodyKeyReveal(Container): + # Index of the validator whose key is being revealed + revealer_index: ValidatorIndex + # Reveal (masked signature) + reveal: BLSSignature +``` + +#### `EarlyDerivedSecretReveal` + +Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). + +```python +class EarlyDerivedSecretReveal(Container): + # Index of the validator whose key is being revealed + revealed_index: ValidatorIndex + # RANDAO epoch of the key that is being revealed + epoch: Epoch + # Reveal (masked signature) + reveal: BLSSignature + # Index of the validator who revealed (whistleblower) + masker_index: ValidatorIndex + # Mask used to hide the actual reveal signature (prevent reveal from being stolen) + mask: Bytes32 +``` ## Updated containers @@ -285,6 +378,8 @@ class BeaconState(Container): # at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH], EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] + custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS] + custody_chunk_challenge_index: uint64 ``` ## New containers diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 6315955f58..f069c4cd27 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -65,6 +65,9 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | | `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours | +| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 16,384) | epochs | ~9 days | +| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | ### Max operations per block @@ -72,6 +75,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | - | - | | `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) | | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` | +| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | | `MAX_CUSTODY_SLASHINGS` | `1` | ### Reward and penalty quotients @@ -93,61 +97,20 @@ The following types are defined, mapping into `DomainType` (little endian): ### New Beacon Chain operations -#### `CustodySlashing` - -```python -class CustodySlashing(Container): - # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. - # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. - data_index: uint64 - malefactor_index: ValidatorIndex - malefactor_secret: BLSSignature - whistleblower_index: ValidatorIndex - shard_transition: ShardTransition - attestation: Attestation - data: ByteList[MAX_SHARD_BLOCK_SIZE] -``` - -#### `SignedCustodySlashing` - -```python -class SignedCustodySlashing(Container): - message: CustodySlashing - signature: BLSSignature -``` - - -#### `CustodyKeyReveal` - -```python -class CustodyKeyReveal(Container): - # Index of the validator whose key is being revealed - revealer_index: ValidatorIndex - # Reveal (masked signature) - reveal: BLSSignature -``` - -#### `EarlyDerivedSecretReveal` +## Helpers -Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). +### `replace_empty_or_append` ```python -class EarlyDerivedSecretReveal(Container): - # Index of the validator whose key is being revealed - revealed_index: ValidatorIndex - # RANDAO epoch of the key that is being revealed - epoch: Epoch - # Reveal (masked signature) - reveal: BLSSignature - # Index of the validator who revealed (whistleblower) - masker_index: ValidatorIndex - # Mask used to hide the actual reveal signature (prevent reveal from being stolen) - mask: Bytes32 +def replace_empty_or_append(list: List, new_element: Any) -> int: + for i in range(len(list)): + if list[i] == empty(typeof(new_element)): + list[i] = new_element + return i + list.append(new_element) + return len(list) - 1 ``` - -## Helpers - ### `legendre_bit` Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this. @@ -248,6 +211,84 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - for_ops(body.custody_slashings, process_custody_slashing) ``` +#### 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 + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation)) + # Verify it is not too late to challenge + assert (challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY + >= get_current_epoch(state)) + responder = state.validators[challenge.responder_index] + assert (responder.exit_epoch == FAR_FUTURE_EPOCH + or responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY >= get_current_epoch(state)) + # Verify responder is slashable + assert is_slashable_validator(responder, get_current_epoch(state)) + # Verify the responder participated in the attestation + attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits) + assert challenge.responder_index in attesters + # Verify shard transition is correctly given + assert hash_tree_root(challenge.shard_transition) == challenge.attestation.data.shard_transition_root + data_root = challenge.shard_transition.shard_data_roots[challenge.data_index] + # Verify the challenge is not a duplicate + for record in state.custody_chunk_challenge_records: + assert ( + record.data_root != challenge.attestation.data.crosslink.data_root or + record.chunk_index != challenge.chunk_index + ) + # Verify depth + transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK + depth = ceillog2(transition_chunks) + assert challenge.chunk_index < transition_chunks + # Add new chunk challenge record + new_record = CustodyChunkChallengeRecord( + challenge_index=state.custody_chunk_challenge_index, + challenger_index=get_beacon_proposer_index(state), + responder_index=challenge.responder_index, + inclusion_epoch=get_current_epoch(state), + data_root=challenge.shard_transition.shard_data_roots[challenge.data_index], + depth=depth, + chunk_index=challenge.chunk_index, + ) + replace_empty_or_append(state.custody_chunk_challenge_records, new_record) + + state.custody_chunk_challenge_index += 1 + # Postpone responder withdrawability + responder.withdrawable_epoch = FAR_FUTURE_EPOCH +``` + +#### Custody chunk response + +```python +def process_chunk_challenge_response(state: BeaconState, + response: CustodyChunkResponse) -> None: + + challenge = next((record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index), None) + assert(challenge is not None) + + # Verify chunk index + assert response.chunk_index == challenge.chunk_index + # Verify the chunk matches the crosslink data root + assert is_valid_merkle_branch( + leaf=hash_tree_root(response.chunk), + branch=response.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, get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT) +``` + #### Custody key reveals ```python @@ -431,14 +472,22 @@ Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`: def process_reveal_deadlines(state: BeaconState) -> None: epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal: - # ------------------ WARNING ----------------------- # - # UNSAFE REMOVAL OF SLASHING TO PRIORITIZE PHASE 0 CI # - # Must find generic way to handle key reveals in tests # - # ---------------------------------------------------- # - - # slash_validator(state, ValidatorIndex(index)) - pass + if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): + slash_validator(state, ValidatorIndex(index)) +``` + +Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`: + +```python +# begin insert @process_challenge_deadlines + process_challenge_deadlines(state) +# end insert @process_challenge_deadlines +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[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord() ``` ### Final updates diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8215f5c5b0..325fee2602 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', state -def build_attestation_data(spec, state, slot, index): +def build_attestation_data(spec, state, slot, index, shard_transition_root=None): assert state.slot >= slot if slot == state.slot: @@ -72,6 +72,7 @@ def build_attestation_data(spec, state, slot, index): beacon_block_root=block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), + shard_transition_root=shard_transition_root, ) @@ -89,7 +90,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False) return attestation -def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): ''' Construct on-time attestation for next slot ''' @@ -98,10 +99,10 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition_root=shard_transition_root) -def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False): +def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): ''' Construct on-time attestation for next slot ''' @@ -110,16 +111,16 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False) if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition_root=shard_transition_root) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True): +def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition_root=None): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index) + attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=shard_transition_root) beacon_committee = spec.get_beacon_committee( state, diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 7c51675cdc..45c29be9a0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,8 +1,8 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector +from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof +from eth2spec.utils.merkle_minimal import get_merkle_root, get_merkle_tree, get_merkle_proof from remerkleable.core import pack_bits_to_chunks from remerkleable.tree import subtree_fill_to_contents, get_depth @@ -61,7 +61,7 @@ def bitlist_from_int(max_len, num_bits, n): return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)]) -def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False): +def get_valid_custody_slashing(spec, state, attestation, invalid_custody_bit=False): beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, @@ -96,21 +96,39 @@ def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False) ) +def get_valid_chunk_challenge(spec, state, attestation, shard_transition): + shard = spec.compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) + crosslink_committee = spec.get_beacon_committee( + state, + attestation.data.slot, + attestation.data.index + ) + responder_index = crosslink_committee[0] + data_index = len(shard_transition.shard_block_lengths) - 1 + + chunk_count = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + + return spec.CustodyChunkChallenge( + responder_index=responder_index, + attestation=attestation, + chunk_index=chunk_count - 1, + data_index=data_index, + shard_transition=shard_transition, + ) + + def custody_chunkify(spec, x): chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)] chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0") return chunks -def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False): +def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, challenge_index, + invalid_chunk_data=False): + custody_data = get_custody_test_vector(block_length) chunks = custody_chunkify(spec, custody_data) - chunk_index = len(chunks) - 1 - chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index]) - - while chunk_bit == bit_challenge.chunk_bits[chunk_index] ^ invalid_chunk_bit: - chunk_index -= 1 - chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index]) + chunk_index = chunk_challenge.chunk_index chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks] chunks_hash_tree_roots += [ @@ -120,31 +138,29 @@ def get_valid_custody_response(spec, state, bit_challenge, custody_data, challen data_branch = get_merkle_proof(data_tree, chunk_index) - bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK - print(bitlist_chunk_index) - bitlist_chunk_nodes = pack_bits_to_chunks(bit_challenge.chunk_bits) - bitlist_tree = subtree_fill_to_contents(bitlist_chunk_nodes, get_depth(spec.MAX_CUSTODY_CHUNKS)) - print(bitlist_tree) - bitlist_chunk_branch = None # TODO; extract proof from merkle tree - - bitlist_chunk_index = chunk_index // 256 - - chunk_bits_leaf = Bitvector[256](bit_challenge.chunk_bits[bitlist_chunk_index * 256: - (bitlist_chunk_index + 1) * 256]) - - return spec.CustodyResponse( + return spec.CustodyChunkResponse( challenge_index=challenge_index, chunk_index=chunk_index, chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]), - data_branch=data_branch, - chunk_bits_branch=bitlist_chunk_branch, - chunk_bits_leaf=chunk_bits_leaf, + branch=data_branch, ) def get_custody_test_vector(bytelength): - ints = bytelength // 4 - return b"".join(i.to_bytes(4, "little") for i in range(ints)) + ints = bytelength // 4 + 1 + return (b"".join(i.to_bytes(4, "little") for i in range(ints)))[:bytelength] + + +def get_shard_transition(spec, start_slot, block_lengths): + b = [hash_tree_root(ByteVector[x](get_custody_test_vector(x))) for x in block_lengths] + shard_transition = spec.ShardTransition( + start_slot=start_slot, + shard_block_lengths=block_lengths, + shard_data_roots=b, + shard_states=[spec.Root() for x in block_lengths], + proposer_signature_aggregate=spec.BLSSignature(), + ) + return shard_transition def get_custody_merkle_root(data): diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py index 0e16e1face..061553ef9a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py @@ -5,21 +5,23 @@ import eth2spec.test.helpers.attestations as phase0_attestations -def get_valid_on_time_attestation(spec, state, index=None, signed=False): +def get_valid_on_time_attestation(spec, state, index=None, signed=False, shard_transition_root=None): ''' Construct on-time attestation for next slot ''' if index is None: index = 0 - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) + attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False, shard_transition_root=shard_transition_root) shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) + print(offset_slots) for _ in offset_slots: attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) ) + print(len(attestation.custody_bits_blocks)) if signed: sign_attestation(spec, state, attestation) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py new file mode 100644 index 0000000000..09b25a1add --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -0,0 +1,238 @@ +from eth2spec.test.helpers.custody import ( + get_valid_chunk_challenge, + get_valid_custody_chunk_response, + get_custody_test_vector, + get_custody_merkle_root, + get_shard_transition, +) +from eth2spec.test.helpers.attestations import ( + get_valid_on_time_attestation, +) +from eth2spec.test.helpers.state import transition_to +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, + expect_assertion_error, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing + + +def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=True): + """ + Run ``process_chunk_challenge``, yielding: + - pre-state ('pre') + - CustodyBitChallenge ('custody_chunk_challenge') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_chunk_challenge', custody_chunk_challenge + + if not valid: + expect_assertion_error(lambda: spec.custody_chunk_challenge(state, custody_chunk_challenge)) + yield 'post', None + return + + spec.process_chunk_challenge(state, custody_chunk_challenge) + + assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].responder_index == \ + custody_chunk_challenge.responder_index + assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].chunk_index == \ + custody_chunk_challenge.chunk_index + + yield 'post', state + + +def run_custody_chunk_response_processing(spec, state, custody_response, valid=True): + """ + Run ``process_chunk_challenge_response``, yielding: + - pre-state ('pre') + - CustodyResponse ('custody_response') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_response', custody_response + + if not valid: + expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) + yield 'post', None + return + + spec.process_chunk_challenge_response(state, custody_response) + + assert state.custody_chunk_challenge_records[custody_response.challenge_index] == spec.CustodyChunkChallengeRecord() + + yield 'post', state + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_challenge_appended(spec, state): + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_chunks = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[data_index]) + shard_root = get_custody_merkle_root(test_vector) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_multiple_epochs_custody(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_many_epochs_custody(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_off_chain_attestation(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + + yield from run_custody_chunk_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_multiple_epochs(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + + yield from run_custody_chunk_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_many_epochs(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + + yield from run_custody_chunk_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py new file mode 100644 index 0000000000..358aa5c0d1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -0,0 +1,349 @@ +from eth2spec.test.helpers.custody import ( + get_valid_bit_challenge, + get_valid_custody_bit_response, + get_custody_test_vector, + get_custody_merkle_root, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, +) +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.helpers.state import next_epoch, get_balance +from eth2spec.test.helpers.block import apply_empty_block +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, + expect_assertion_error, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing + + +def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True): + """ + Run ``process_bit_challenge``, yielding: + - pre-state ('pre') + - CustodyBitChallenge ('custody_bit_challenge') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_bit_challenge', custody_bit_challenge + + if not valid: + expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge)) + yield 'post', None + return + + spec.process_bit_challenge(state, custody_bit_challenge) + + assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].chunk_bits_merkle_root == \ + hash_tree_root(custody_bit_challenge.chunk_bits) + assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].challenger_index == \ + custody_bit_challenge.challenger_index + assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].responder_index == \ + custody_bit_challenge.responder_index + + yield 'post', state + + +def run_custody_bit_response_processing(spec, state, custody_response, valid=True): + """ + Run ``process_bit_challenge_response``, yielding: + - pre-state ('pre') + - CustodyResponse ('custody_response') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield 'pre', state + yield 'custody_response', custody_response + + if not valid: + expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) + yield 'post', None + return + + challenge = state.custody_bit_challenge_records[custody_response.challenge_index] + pre_slashed_balance = get_balance(state, challenge.challenger_index) + + spec.process_custody_response(state, custody_response) + + slashed_validator = state.validators[challenge.challenger_index] + + assert slashed_validator.slashed + assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + assert get_balance(state, challenge.challenger_index) < pre_slashed_balance + yield 'post', state + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_challenge_appended(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_multiple_epochs_custody(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 3 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_many_epochs_custody(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 100 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_off_chain_attestation(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_invalid_custody_bit_challenge(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True) + + yield from run_bit_challenge_processing(spec, state, challenge, valid=False) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_max_reveal_lateness_1(spec, state): + next_epoch(spec, state) + apply_empty_block(spec, state) + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + responder_index = challenge.responder_index + target_epoch = attestation.data.target.epoch + + state.validators[responder_index].max_reveal_lateness = 3 + + latest_reveal_epoch = spec.get_randao_epoch_for_custody_period( + spec.get_custody_period_for_validator(state, responder_index, target_epoch), + responder_index + ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness + + while spec.get_current_epoch(state) < latest_reveal_epoch - 2: + next_epoch(spec, state) + apply_empty_block(spec, state) + + yield from run_bit_challenge_processing(spec, state, challenge) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_max_reveal_lateness_2(spec, state): + next_epoch(spec, state) + apply_empty_block(spec, state) + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + responder_index = challenge.responder_index + + state.validators[responder_index].max_reveal_lateness = 3 + + for i in range(spec.get_randao_epoch_for_custody_period( + spec.get_custody_period_for_validator(state, responder_index), + responder_index + ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1): + next_epoch(spec, state) + apply_empty_block(spec, state) + + yield from run_bit_challenge_processing(spec, state, challenge, False) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + bit_challenge_index = state.custody_bit_challenge_index - 1 + + custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + + yield from run_custody_bit_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_multiple_epochs(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 3 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + bit_challenge_index = state.custody_bit_challenge_index - 1 + + custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + + yield from run_custody_bit_response_processing(spec, state, custody_response) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_custody_response_many_epochs(spec, state): + state.slot = spec.SLOTS_PER_EPOCH * 100 + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + + _, _, _ = run_attestation_processing(spec, state, attestation) + + state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + bit_challenge_index = state.custody_bit_challenge_index - 1 + + custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + + yield from run_custody_bit_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py new file mode 100644 index 0000000000..d200ed2b36 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py @@ -0,0 +1,319 @@ +from eth2spec.test.helpers.custody import ( + get_valid_bit_challenge, + get_valid_chunk_challenge, + get_valid_custody_bit_response, + get_valid_custody_chunk_response, + get_valid_custody_key_reveal, + get_custody_test_vector, + get_custody_merkle_root, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, +) +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.block import apply_empty_block +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with + +from eth2spec.test.phase_1.block_processing.test_process_bit_challenge import ( + run_bit_challenge_processing, + run_custody_bit_response_processing, +) +from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( + run_chunk_challenge_processing, + run_custody_chunk_response_processing, +) +from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing + + +def run_process_final_custody_updates(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_final_custody_updates') + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_delay(spec, state): + spec.initiate_validator_exit(state, 0) + assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): + spec.initiate_validator_exit(state, 0) + assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[0].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[0].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator(state, 0, state.validators[0].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=0) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_suspend_after_bit_challenge(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_chunk_challenge(spec, state, attestation) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_resume_after_bit_challenge_response(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + attestation.custody_bits[0] = 0 + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_bit_challenge(spec, state, attestation) + + _, _, _ = run_bit_challenge_processing(spec, state, challenge) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + bit_challenge_index = state.custody_bit_challenge_index - 1 + response = get_valid_custody_bit_response( + spec, + state, + challenge, + test_vector, + bit_challenge_index, + invalid_chunk_bit=False) + + _, _, _ = run_custody_bit_response_processing(spec, state, response) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state): + state.slot = spec.SLOTS_PER_EPOCH + + attestation = get_valid_attestation(spec, state, signed=True) + + test_vector = get_custody_test_vector( + spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) + shard_root = get_custody_merkle_root(test_vector) + attestation.data.crosslink.data_root = shard_root + state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + next_epoch(spec, state) + apply_empty_block(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_crosslink_committee( + state, + attestation.data.target.epoch, + attestation.data.crosslink.shard + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch(spec, state) + apply_empty_block(spec, state) + + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + state, + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + challenge = get_valid_chunk_challenge(spec, state, attestation) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + next_epoch(spec, state) + apply_empty_block(spec, state) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + chunk_challenge_index = state.custody_chunk_challenge_index - 1 + response = get_valid_custody_chunk_response(spec, state, challenge, test_vector, chunk_challenge_index) + + _, _, _ = run_custody_chunk_response_processing(spec, state, response) + + yield from run_process_final_custody_updates(spec, state) + + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py new file mode 100644 index 0000000000..688b046c8e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py @@ -0,0 +1,46 @@ +from eth2spec.test.helpers.custody import ( + get_valid_custody_key_reveal, +) +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing + + +def run_process_challenge_deadlines(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_slashed_after_reveal_deadline(spec, state): + assert state.validators[0].slashed == 0 + + state.slot += ((spec.CHUNK_RESPONSE_DEADLINE + spec.EPOCHS_PER_CUSTODY_PERIOD) + * spec.SLOTS_PER_EPOCH) + next_epoch(spec, state) + + yield from run_process_challenge_deadlines(spec, state) + + assert state.validators[0].slashed == 1 + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_not_slashed_after_reveal(spec, state): + state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH + custody_key_reveal = get_valid_custody_key_reveal(spec, state) + + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + assert state.validators[0].slashed == 0 + + state.slot += spec.CHUNK_RESPONSE_DEADLINE * spec.SLOTS_PER_EPOCH + next_epoch(spec, state) + + yield from run_process_challenge_deadlines(spec, state) + + assert state.validators[0].slashed == 0 From 2449db1bb6457c0b9e96bd7ccd3c3e3873050a1d Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Mon, 27 Apr 2020 16:08:49 +0100 Subject: [PATCH 010/236] Phase 1 block tests are working --- specs/phase1/beacon-chain.md | 9 +- specs/phase1/custody-game.md | 30 +- .../eth2spec/test/helpers/attestations.py | 48 ++- .../pyspec/eth2spec/test/helpers/custody.py | 81 +++-- .../test_process_chunk_challenge.py | 14 +- .../test_process_custody_key_reveal.py | 16 - .../test_process_custody_slashing.py | 343 +++++------------- 7 files changed, 200 insertions(+), 341 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 18c5f347fd..fc208cbaa5 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -111,7 +111,7 @@ Configuration is not namespaced. Instead it is strictly an extension; | `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | | `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | | `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes | -| `MAX_CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - | +| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - | @@ -137,7 +137,6 @@ class CustodyChunkChallengeRecord(Container): responder_index: ValidatorIndex inclusion_epoch: Epoch data_root: Root - depth: uint64 chunk_index: uint64 ``` @@ -148,7 +147,7 @@ class CustodyChunkResponse(Container): challenge_index: uint64 chunk_index: uint64 chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] - branch: List[Root, MAX_CUSTODY_RESPONSE_DEPTH] + branch: Vector[Root, CUSTODY_RESPONSE_DEPTH] ``` #### `CustodySlashing` @@ -281,7 +280,8 @@ class Validator(Container): # (of the particular validator) in which the validator is activated # = get_custody_period_for_validator(...) next_custody_secret_to_reveal: uint64 - max_reveal_lateness: Epoch + # TODO: The max_reveal_lateness doesn't really make sense anymore. + # So how do we incentivise early custody key reveals now? ``` ### Extended `BeaconBlockBody` @@ -428,6 +428,7 @@ class ShardTransition(Container): # Shard block lengths shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Shard data roots + # The root is of ByteVector[MAX_] shard_data_roots: List[Bytes32, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Intermediate shard states shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION] diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index f069c4cd27..3047fb263c 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -17,11 +17,6 @@ - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) - - [New Beacon Chain operations](#new-beacon-chain-operations) - - [`CustodySlashing`](#custodyslashing) - - [`SignedCustodySlashing`](#signedcustodyslashing) - - [`CustodyKeyReveal`](#custodykeyreveal) - - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) @@ -64,7 +59,6 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days | | `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | -| `MAX_REVEAL_LATENESS_DECREMENT` | `2**7` (= 128) | epochs | ~14 hours | | `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | | `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 16,384) | epochs | ~9 days | | `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | @@ -168,7 +162,7 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: ### `compute_custody_bit` ```python -def compute_custody_bit(key: BLSSignature, data: bytes) -> bit: +def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit: secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) @@ -243,7 +237,6 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge ) # Verify depth transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK - depth = ceillog2(transition_chunks) assert challenge.chunk_index < transition_chunks # Add new chunk challenge record new_record = CustodyChunkChallengeRecord( @@ -252,7 +245,6 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge responder_index=challenge.responder_index, inclusion_epoch=get_current_epoch(state), data_root=challenge.shard_transition.shard_data_roots[challenge.data_index], - depth=depth, chunk_index=challenge.chunk_index, ) replace_empty_or_append(state.custody_chunk_challenge_records, new_record) @@ -277,7 +269,7 @@ def process_chunk_challenge_response(state: BeaconState, assert is_valid_merkle_branch( leaf=hash_tree_root(response.chunk), branch=response.branch, - depth=challenge.depth, + depth=CUSTODY_RESPONSE_DEPTH, index=response.chunk_index, root=challenge.data_root, ) @@ -311,18 +303,6 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> signing_root = compute_signing_root(epoch_to_sign, domain) assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal) - # Decrement max reveal lateness if response is timely - if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state): - if revealer.max_reveal_lateness >= MAX_REVEAL_LATENESS_DECREMENT: - revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT - else: - revealer.max_reveal_lateness = 0 - else: - revealer.max_reveal_lateness = max( - revealer.max_reveal_lateness, - get_current_epoch(state) - epoch_to_sign - EPOCHS_PER_CUSTODY_PERIOD - ) - # Process reveal revealer.next_custody_secret_to_reveal += 1 @@ -417,13 +397,15 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) # TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding. + # ??? What does this mean? # TODO: can do a single combined merkle proof of data being attested. # Verify the shard transition is indeed attested by the attestation shard_transition = custody_slashing.shard_transition - assert hash_tree_root(shard_transition) == attestation.shard_transition_root + assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index] + assert custody_slashing.data.get_backing().get_left().merkle_root() == shard_transition.shard_data_roots[custody_slashing.data_index] + assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] # Verify existence and participation of claimed malefactor attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 325fee2602..5e536002f5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -6,6 +6,8 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist +from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.test.helpers.custody import get_custody_test_vector def run_attestation_processing(spec, state, attestation, valid=True): @@ -72,17 +74,43 @@ def build_attestation_data(spec, state, slot, index, shard_transition_root=None) beacon_block_root=block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), - shard_transition_root=shard_transition_root, + shard_transition_root=shard_transition_root if shard_transition_root else spec.Root(), ) -def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False): +def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None, valid_custody_bits=None): shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - for offset_slot in offset_slots: + + if valid_custody_bits is not None: + beacon_committee = spec.get_beacon_committee( + state, + attestation.data.slot, + attestation.data.index, + ) + current_epoch = spec.get_current_epoch(state) + custody_secrets = [None for i in beacon_committee] + for i in range(len(beacon_committee)): + validator = state.validators[beacon_committee[i]] + + period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch) + + epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i]) + + domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) + signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) + custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root) + + + for i, offset_slot in enumerate(offset_slots): attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) ) + if valid_custody_bits is not None: + test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i]) + for j in range(len(attestation.custody_bits_blocks[i])): + if attestation.aggregation_bits[j]: + attestation.custody_bits_blocks[i][j] = spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) if signed: sign_attestation(spec, state, attestation) @@ -90,7 +118,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False) return attestation -def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None, valid_custody_bits=None): ''' Construct on-time attestation for next slot ''' @@ -99,10 +127,10 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition_root=shard_transition_root) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) -def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition_root=None): +def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None): ''' Construct on-time attestation for next slot ''' @@ -111,16 +139,16 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition_root=shard_transition_root) + return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition=shard_transition) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition_root=None): +def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition=None, valid_custody_bits=None): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=shard_transition_root) + attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=hash_tree_root(shard_transition) if shard_transition else spec.Root()) beacon_committee = spec.get_beacon_committee( state, @@ -140,7 +168,7 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe sign_attestation(spec, state, attestation) if spec.fork == 'phase1' and on_time: - attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 45c29be9a0..f4b2d6a3d5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,10 +1,10 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList +from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList, uint64 from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.merkle_minimal import get_merkle_root, get_merkle_tree, get_merkle_proof from remerkleable.core import pack_bits_to_chunks -from remerkleable.tree import subtree_fill_to_contents, get_depth +from remerkleable.tree import subtree_fill_to_contents, get_depth, Node, Gindex, gindex_bit_iter, Root BYTES_PER_CHUNK = 32 @@ -61,39 +61,45 @@ def bitlist_from_int(max_len, num_bits, n): return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)]) -def get_valid_custody_slashing(spec, state, attestation, invalid_custody_bit=False): +def get_valid_custody_slashing(spec, state, attestation, shard_transition, invalid_custody_bit=False): beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, - attestation.data.crosslink.shard, + attestation.data.index, ) - responder_index = beacon_committee[0] - challenger_index = beacon_committee[-1] + malefactor_index = beacon_committee[0] + whistleblower_index = beacon_committee[-1] epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch, - responder_index) + malefactor_index) # Generate the responder key domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch) signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) - responder_key = bls.Sign(privkeys[responder_index], signing_root) + malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root) + data_index = 0 + data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) + print(hash_tree_root(data)) + print(data.get_backing().get_left().merkle_root()) - chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink) - - chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, 0) - - n = 0 - while spec.get_chunk_bits_root(chunk_bits) == attestation.custody_bits[0] ^ invalid_custody_bit: - chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, n) - n += 1 - - return spec.CustodyBitChallenge( - responder_index=responder_index, + slashing = spec.CustodySlashing( + data_index=data_index, + malefactor_index=malefactor_index, + malefactor_secret=malefactor_key, + whistleblower_index=whistleblower_index, + shard_transition=shard_transition, attestation=attestation, - challenger_index=challenger_index, - responder_key=responder_key, - chunk_bits=chunk_bits, + data=data, ) + slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING) + slashing_root = spec.compute_signing_root(slashing, domain) + + signed_slashing = spec.SignedCustodySlashing( + message=slashing, + signature=bls.Sign(privkeys[whistleblower_index], slashing_root) + ) + + return signed_slashing def get_valid_chunk_challenge(spec, state, attestation, shard_transition): @@ -123,20 +129,35 @@ def custody_chunkify(spec, x): return chunks +def build_proof(anchor, leaf_index): + if leaf_index <= 1: + return [] # Nothing to prove / invalid index + node = anchor + proof = [] + # Walk down, top to bottom to the leaf + bit_iter, _ = gindex_bit_iter(leaf_index) + for bit in bit_iter: + # Always take the opposite hand for the proof. + # 1 = right as leaf, thus get left + if bit: + proof.append(node.get_left().merkle_root()) + node = node.get_right() + else: + proof.append(node.get_right().merkle_root()) + node = node.get_left() + + return list(reversed(proof)) + + def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, challenge_index, invalid_chunk_data=False): custody_data = get_custody_test_vector(block_length) + custody_data_block = ByteList[spec.MAX_SHARD_BLOCK_SIZE](custody_data) chunks = custody_chunkify(spec, custody_data) chunk_index = chunk_challenge.chunk_index - chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks] - chunks_hash_tree_roots += [ - hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK)) - for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))] - data_tree = get_merkle_tree(chunks_hash_tree_roots) - - data_branch = get_merkle_proof(data_tree, chunk_index) + data_branch = build_proof(custody_data_block.get_backing().get_left(), chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH) return spec.CustodyChunkResponse( challenge_index=challenge_index, @@ -152,7 +173,7 @@ def get_custody_test_vector(bytelength): def get_shard_transition(spec, start_slot, block_lengths): - b = [hash_tree_root(ByteVector[x](get_custody_test_vector(x))) for x in block_lengths] + b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)).get_backing().get_left().merkle_root() for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, shard_block_lengths=block_lengths, diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py index 09b25a1add..d409b468f5 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -75,7 +75,7 @@ def test_challenge_appended(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_chunks = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[data_index]) @@ -101,7 +101,7 @@ def test_multiple_epochs_custody(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -123,7 +123,7 @@ def test_many_epochs_custody(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -145,7 +145,7 @@ def test_off_chain_attestation(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) @@ -163,7 +163,7 @@ def test_custody_response(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -191,7 +191,7 @@ def test_custody_response_multiple_epochs(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -219,7 +219,7 @@ def test_custody_response_many_epochs(spec, state): offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition_root=hash_tree_root(shard_transition)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py index 8c2436d5b6..fd16a344ca 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py @@ -28,30 +28,14 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru pre_next_custody_secret_to_reveal = \ state.validators[revealer_index].next_custody_secret_to_reveal - pre_reveal_lateness = state.validators[revealer_index].max_reveal_lateness spec.process_custody_key_reveal(state, custody_key_reveal) post_next_custody_secret_to_reveal = \ state.validators[revealer_index].next_custody_secret_to_reveal - post_reveal_lateness = state.validators[revealer_index].max_reveal_lateness assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1 - if spec.get_current_epoch(state) > spec.get_randao_epoch_for_custody_period( - pre_next_custody_secret_to_reveal, - revealer_index - ) + spec.EPOCHS_PER_CUSTODY_PERIOD: - assert post_reveal_lateness > 0 - if pre_reveal_lateness == 0: - assert post_reveal_lateness == spec.get_current_epoch(state) - spec.get_randao_epoch_for_custody_period( - pre_next_custody_secret_to_reveal, - revealer_index - ) - spec.EPOCHS_PER_CUSTODY_PERIOD - else: - if pre_reveal_lateness > 0: - assert post_reveal_lateness < pre_reveal_lateness - yield 'post', state diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py index 358aa5c0d1..706fd8f493 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -1,14 +1,15 @@ from eth2spec.test.helpers.custody import ( - get_valid_bit_challenge, - get_valid_custody_bit_response, + get_valid_custody_slashing, get_custody_test_vector, get_custody_merkle_root, + get_shard_transition, ) from eth2spec.test.helpers.attestations import ( - get_valid_attestation, + get_valid_on_time_attestation, ) from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.helpers.state import next_epoch, get_balance +from eth2spec.utils.ssz.ssz_typing import ByteList +from eth2spec.test.helpers.state import next_epoch, get_balance, transition_to from eth2spec.test.helpers.block import apply_empty_block from eth2spec.test.context import ( with_all_phases_except, @@ -18,332 +19,174 @@ from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing -def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True): +def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True): """ Run ``process_bit_challenge``, yielding: - pre-state ('pre') - - CustodyBitChallenge ('custody_bit_challenge') + - CustodySlashing ('custody_slashing') - post-state ('post'). If ``valid == False``, run expecting ``AssertionError`` """ yield 'pre', state - yield 'custody_bit_challenge', custody_bit_challenge + yield 'custody_slashing', custody_slashing if not valid: - expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge)) + expect_assertion_error(lambda: spec.process_custody_slashing(state, custody_slashing)) yield 'post', None return - spec.process_bit_challenge(state, custody_bit_challenge) + if correct: + pre_slashed_balance = get_balance(state, custody_slashing.message.malefactor_index) + else: + pre_slashed_balance = get_balance(state, custody_slashing.message.whistleblower_index) - assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].chunk_bits_merkle_root == \ - hash_tree_root(custody_bit_challenge.chunk_bits) - assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].challenger_index == \ - custody_bit_challenge.challenger_index - assert state.custody_bit_challenge_records[state.custody_bit_challenge_index - 1].responder_index == \ - custody_bit_challenge.responder_index - - yield 'post', state - - -def run_custody_bit_response_processing(spec, state, custody_response, valid=True): - """ - Run ``process_bit_challenge_response``, yielding: - - pre-state ('pre') - - CustodyResponse ('custody_response') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_response', custody_response - - if not valid: - expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) - yield 'post', None - return - - challenge = state.custody_bit_challenge_records[custody_response.challenge_index] - pre_slashed_balance = get_balance(state, challenge.challenger_index) - - spec.process_custody_response(state, custody_response) - - slashed_validator = state.validators[challenge.challenger_index] + spec.process_custody_slashing(state, custody_slashing) + if correct: + slashed_validator = state.validators[custody_slashing.message.malefactor_index] + assert get_balance(state, custody_slashing.message.malefactor_index) < pre_slashed_balance + else: + slashed_validator = state.validators[custody_slashing.message.whistleblower_index] + assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance + assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - assert get_balance(state, challenge.challenger_index) < pre_slashed_balance yield 'post', state @with_all_phases_except(['phase0']) @spec_state_test -def test_challenge_appended(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_multiple_epochs_custody(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 3 - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_many_epochs_custody(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 100 - attestation = get_valid_attestation(spec, state, signed=True) +def test_custody_slashing(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + data_index = 0 -@with_all_phases_except(['phase0']) -@spec_state_test -def test_off_chain_attestation(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) - - yield from run_bit_challenge_processing(spec, state, challenge) + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @with_all_phases_except(['phase0']) @spec_state_test -def test_invalid_custody_bit_challenge(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) +def test_incorrect_custody_slashing(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=True) - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True) - - yield from run_bit_challenge_processing(spec, state, challenge, valid=False) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_max_reveal_lateness_1(spec, state): - next_epoch(spec, state) - apply_empty_block(spec, state) - - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - next_epoch(spec, state) - apply_empty_block(spec, state) - - _, _, _ = run_attestation_processing(spec, state, attestation) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - challenge = get_valid_bit_challenge(spec, state, attestation) + data_index = 0 - responder_index = challenge.responder_index - target_epoch = attestation.data.target.epoch + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - state.validators[responder_index].max_reveal_lateness = 3 - - latest_reveal_epoch = spec.get_randao_epoch_for_custody_period( - spec.get_custody_period_for_validator(state, responder_index, target_epoch), - responder_index - ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - - while spec.get_current_epoch(state) < latest_reveal_epoch - 2: - next_epoch(spec, state) - apply_empty_block(spec, state) - - yield from run_bit_challenge_processing(spec, state, challenge) + yield from run_custody_slashing_processing(spec, state, slashing, correct=False) @with_all_phases_except(['phase0']) @spec_state_test -def test_max_reveal_lateness_2(spec, state): - next_epoch(spec, state) - apply_empty_block(spec, state) - - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 +def test_multiple_epochs_custody(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) - next_epoch(spec, state) - apply_empty_block(spec, state) + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - challenge = get_valid_bit_challenge(spec, state, attestation) - - responder_index = challenge.responder_index + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - state.validators[responder_index].max_reveal_lateness = 3 + data_index = 0 - for i in range(spec.get_randao_epoch_for_custody_period( - spec.get_custody_period_for_validator(state, responder_index), - responder_index - ) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1): - next_epoch(spec, state) - apply_empty_block(spec, state) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - yield from run_bit_challenge_processing(spec, state, challenge, False) + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @with_all_phases_except(['phase0']) @spec_state_test -def test_custody_response(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 +def test_many_epochs_custody(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH* 100) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - _, _, _ = run_bit_challenge_processing(spec, state, challenge) + data_index = 0 - bit_challenge_index = state.custody_bit_challenge_index - 1 + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) - - yield from run_custody_bit_response_processing(spec, state, custody_response) + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @with_all_phases_except(['phase0']) @spec_state_test -def test_custody_response_multiple_epochs(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 3 - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - - _, _, _ = run_attestation_processing(spec, state, attestation) - - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) +def test_off_chain_attestation(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) - _, _, _ = run_bit_challenge_processing(spec, state, challenge) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - bit_challenge_index = state.custody_bit_challenge_index - 1 + data_index = 0 - custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - yield from run_custody_bit_response_processing(spec, state, custody_response) + yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @with_all_phases_except(['phase0']) @spec_state_test -def test_custody_response_many_epochs(spec, state): - state.slot = spec.SLOTS_PER_EPOCH * 100 - attestation = get_valid_attestation(spec, state, signed=True) +def test_invalid_custody_slashing(spec, state): + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD - - challenge = get_valid_bit_challenge(spec, state, attestation) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - _, _, _ = run_bit_challenge_processing(spec, state, challenge) + data_index = 0 - bit_challenge_index = state.custody_bit_challenge_index - 1 + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - custody_response = get_valid_custody_bit_response(spec, state, challenge, test_vector, bit_challenge_index) + slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() - yield from run_custody_bit_response_processing(spec, state, custody_response) + yield from run_custody_slashing_processing(spec, state, slashing, valid=False) From 0e2931b9b3490027e11a9a4a7c0851466d533d5d Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 28 Apr 2020 01:09:20 +0100 Subject: [PATCH 011/236] All tests passed --- configs/minimal.yaml | 2 - specs/phase1/beacon-chain.md | 54 +++++ specs/phase1/custody-game.md | 29 ++- specs/phase1/phase1-fork.md | 2 +- .../pyspec/eth2spec/test/helpers/custody.py | 5 +- .../test_process_final_custody_updates.py | 213 +++--------------- 6 files changed, 121 insertions(+), 184 deletions(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 256c2b3fa0..5f8b6b8a22 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -208,8 +208,6 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 EPOCHS_PER_CUSTODY_PERIOD: 8 # 2**11 (= 2,048) epochs CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# 2**7 (= 128) epochs -MAX_REVEAL_LATENESS_DECREMENT: 128 # Max operations # 2**8 (= 256) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fc208cbaa5..b6b21b7214 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -282,6 +282,7 @@ class Validator(Container): next_custody_secret_to_reveal: uint64 # TODO: The max_reveal_lateness doesn't really make sense anymore. # So how do we incentivise early custody key reveals now? + all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH ``` ### Extended `BeaconBlockBody` @@ -925,6 +926,59 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: state.previous_epoch_attestations.append(pending_attestation) ``` +##### New deposits + +```python +def process_deposit(state: BeaconState, deposit: Deposit) -> None: + # Verify the Merkle branch + 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 + index=state.eth1_deposit_index, + root=state.eth1_data.deposit_root, + ) + + # Deposits must be processed in order + state.eth1_deposit_index += 1 + + pubkey = deposit.data.pubkey + 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) 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, + ) + 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 + + # Add validator and balance entries + # TODO: This function is duplicated from phase 1 just because the validator definition + # has changed and we need to initialize it properly. Is there a better solution for + # this? + state.validators.append(Validator( + pubkey=pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), + next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), get_current_epoch(state)), + all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, + )) + state.balances.append(amount) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) +``` + ##### New Attester slashing processing ```python diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 3047fb263c..f40e936ce4 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -293,7 +293,12 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index) custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state)) - assert revealer.next_custody_secret_to_reveal < custody_reveal_period + # Only past custody periods can be revealed, except after exiting the exit + # period can be revealed + assert (revealer.next_custody_secret_to_reveal < custody_reveal_period + or (revealer.exit_epoch <= get_current_epoch(state) and + revealer.next_custody_secret_to_reveal + <= get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1))) # Revealed validator is active or exited, but not withdrawn assert is_slashable_validator(revealer, get_current_epoch(state)) @@ -304,6 +309,10 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal) # Process reveal + if (revealer.exit_epoch <= get_current_epoch(state) and + revealer.next_custody_secret_to_reveal + == get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1)): + revealer.all_custody_secrets_revealed_epoch = get_current_epoch(state) revealer.next_custody_secret_to_reveal += 1 # Reward Block Proposer @@ -480,4 +489,22 @@ After `process_final_updates(state)`, additional updates are made for the custod def process_custody_final_updates(state: BeaconState) -> None: # Clean up exposed RANDAO key reveals state.exposed_derived_secrets[get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] + + # Reset withdrawable epochs if challenge records are empty + records = state.custody_chunk_challenge_records + validator_indices_in_records = set( + [record.responder_index for record in records] + ) + for index, validator in enumerate(state.validators): + if validator.exit_epoch != FAR_FUTURE_EPOCH: + if (index in validator_indices_in_records + or validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH): + # Delay withdrawable epochs if challenge records are not empty or not all + # custody secrets revealed + validator.withdrawable_epoch = FAR_FUTURE_EPOCH + else: + # Reset withdrawable epochs if challenge records are empty + if validator.withdrawable_epoch == FAR_FUTURE_EPOCH: + validator.withdrawable_epoch = Epoch(validator.all_custody_secrets_revealed_epoch + + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) ``` diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index 173fceeb47..9d5f282f38 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -79,7 +79,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: exit_epoch=phase0_validator.exit_epoch, withdrawable_epoch=phase0_validator.withdrawable_epoch, next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(i), epoch), - max_reveal_lateness=0, # TODO custody refactor. Outdated? + all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, ) for i, phase0_validator in enumerate(pre.validators) ), balances=pre.balances, diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index f4b2d6a3d5..2d937a2ee8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -37,9 +37,10 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None): ) -def get_valid_custody_key_reveal(spec, state, period=None): +def get_valid_custody_key_reveal(spec, state, period=None, validator_index=None): current_epoch = spec.get_current_epoch(state) - revealer_index = spec.get_active_validator_indices(state, current_epoch)[0] + revealer_index = (spec.get_active_validator_indices(state, current_epoch)[0] + if validator_index is None else validator_index) revealer = state.validators[revealer_index] if period is None: diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py index d200ed2b36..566d795cfa 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py @@ -1,16 +1,15 @@ from eth2spec.test.helpers.custody import ( - get_valid_bit_challenge, get_valid_chunk_challenge, - get_valid_custody_bit_response, get_valid_custody_chunk_response, get_valid_custody_key_reveal, get_custody_test_vector, get_custody_merkle_root, + get_shard_transition ) from eth2spec.test.helpers.attestations import ( - get_valid_attestation, + get_valid_on_time_attestation, ) -from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.state import next_epoch, transition_to from eth2spec.test.helpers.block import apply_empty_block from eth2spec.test.context import ( with_all_phases_except, @@ -19,10 +18,6 @@ from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with -from eth2spec.test.phase_1.block_processing.test_process_bit_challenge import ( - run_bit_challenge_processing, - run_custody_bit_response_processing, -) from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, run_custody_chunk_response_processing, @@ -30,8 +25,8 @@ from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing -def run_process_final_custody_updates(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_final_custody_updates') +def run_process_custody_final_updates(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates') @with_all_phases_except(['phase0']) @@ -40,7 +35,7 @@ def test_validator_withdrawal_delay(spec, state): spec.initiate_validator_exit(state, 0) assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - yield from run_process_final_custody_updates(spec, state) + yield from run_process_custody_final_updates(spec, state) assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH @@ -61,100 +56,39 @@ def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): apply_empty_block(spec, state) while (state.validators[0].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator(state, 0, state.validators[0].exit_epoch - 1)): + <= spec.get_custody_period_for_validator(0, state.validators[0].exit_epoch - 1)): custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=0) _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - yield from run_process_final_custody_updates(spec, state) + yield from run_process_custody_final_updates(spec, state) assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH -@with_all_phases_except(['phase0']) -@spec_state_test -def test_validator_withdrawal_suspend_after_bit_challenge(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - next_epoch(spec, state) - apply_empty_block(spec, state) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - validator_index = spec.get_crosslink_committee( - state, - attestation.data.target.epoch, - attestation.data.crosslink.shard - )[0] - - spec.initiate_validator_exit(state, validator_index) - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - next_epoch(spec, state) - apply_empty_block(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: - next_epoch(spec, state) - apply_empty_block(spec, state) - - while (state.validators[validator_index].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator( - state, - validator_index, - state.validators[validator_index].exit_epoch - 1)): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - next_epoch(spec, state) - apply_empty_block(spec, state) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - _, _, _ = run_bit_challenge_processing(spec, state, challenge) - - yield from run_process_final_custody_updates(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - @with_all_phases_except(['phase0']) @spec_state_test def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - - attestation = get_valid_attestation(spec, state, signed=True) + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - next_epoch(spec, state) - apply_empty_block(spec, state) + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - validator_index = spec.get_crosslink_committee( + validator_index = spec.get_beacon_committee( state, - attestation.data.target.epoch, - attestation.data.crosslink.shard + attestation.data.slot, + attestation.data.index )[0] spec.initiate_validator_exit(state, validator_index) assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - next_epoch(spec, state) - apply_empty_block(spec, state) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH @@ -164,7 +98,6 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): while (state.validators[validator_index].next_custody_secret_to_reveal <= spec.get_custody_period_for_validator( - state, validator_index, state.validators[validator_index].exit_epoch - 1)): custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) @@ -173,108 +106,33 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): next_epoch(spec, state) apply_empty_block(spec, state) - challenge = get_valid_chunk_challenge(spec, state, attestation) + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - yield from run_process_final_custody_updates(spec, state) + yield from run_process_custody_final_updates(spec, state) assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH -@with_all_phases_except(['phase0']) -@spec_state_test -def test_validator_withdrawal_resume_after_bit_challenge_response(spec, state): - state.slot = spec.SLOTS_PER_EPOCH - - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - attestation.custody_bits[0] = 0 - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - next_epoch(spec, state) - apply_empty_block(spec, state) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - validator_index = spec.get_crosslink_committee( - state, - attestation.data.target.epoch, - attestation.data.crosslink.shard - )[0] - - spec.initiate_validator_exit(state, validator_index) - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - next_epoch(spec, state) - apply_empty_block(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: - next_epoch(spec, state) - apply_empty_block(spec, state) - - while (state.validators[validator_index].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator( - state, - validator_index, - state.validators[validator_index].exit_epoch - 1)): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - next_epoch(spec, state) - apply_empty_block(spec, state) - - challenge = get_valid_bit_challenge(spec, state, attestation) - - _, _, _ = run_bit_challenge_processing(spec, state, challenge) - - next_epoch(spec, state) - apply_empty_block(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - bit_challenge_index = state.custody_bit_challenge_index - 1 - response = get_valid_custody_bit_response( - spec, - state, - challenge, - test_vector, - bit_challenge_index, - invalid_chunk_bit=False) - - _, _, _ = run_custody_bit_response_processing(spec, state, response) - - yield from run_process_final_custody_updates(spec, state) - - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - @with_all_phases_except(['phase0']) @spec_state_test def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state): - state.slot = spec.SLOTS_PER_EPOCH + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + data_index = 0 + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) - attestation = get_valid_attestation(spec, state, signed=True) - - test_vector = get_custody_test_vector( - spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK) - shard_root = get_custody_merkle_root(test_vector) - attestation.data.crosslink.data_root = shard_root - state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY - next_epoch(spec, state) - apply_empty_block(spec, state) + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) _, _, _ = run_attestation_processing(spec, state, attestation) - validator_index = spec.get_crosslink_committee( + validator_index = spec.get_beacon_committee( state, - attestation.data.target.epoch, - attestation.data.crosslink.shard + attestation.data.slot, + attestation.data.index )[0] spec.initiate_validator_exit(state, validator_index) @@ -291,7 +149,6 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) while (state.validators[validator_index].next_custody_secret_to_reveal <= spec.get_custody_period_for_validator( - state, validator_index, state.validators[validator_index].exit_epoch - 1)): custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) @@ -300,7 +157,7 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) next_epoch(spec, state) apply_empty_block(spec, state) - challenge = get_valid_chunk_challenge(spec, state, attestation) + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) _, _, _ = run_chunk_challenge_processing(spec, state, challenge) @@ -310,10 +167,10 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH chunk_challenge_index = state.custody_chunk_challenge_index - 1 - response = get_valid_custody_chunk_response(spec, state, challenge, test_vector, chunk_challenge_index) + custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) - _, _, _ = run_custody_chunk_response_processing(spec, state, response) + _, _, _ = run_custody_chunk_response_processing(spec, state, custody_response) - yield from run_process_final_custody_updates(spec, state) + yield from run_process_custody_final_updates(spec, state) assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH From d58d7627b78885035dbbd0300e61e14266074da3 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Thu, 30 Apr 2020 19:25:18 +0100 Subject: [PATCH 012/236] Fix toc --- specs/phase1/beacon-chain.md | 11 ++++++++++- specs/phase1/custody-game.md | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index b6b21b7214..593f9ba0f5 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -12,6 +12,14 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) +- [New containers](#new-containers) + - [`CustodyChunkChallenge`](#custodychunkchallenge) + - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) + - [`CustodyChunkResponse`](#custodychunkresponse) + - [`CustodySlashing`](#custodyslashing) + - [`SignedCustodySlashing`](#signedcustodyslashing) + - [`CustodyKeyReveal`](#custodykeyreveal) + - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) @@ -23,7 +31,7 @@ - [Extended `BeaconBlock`](#extended-beaconblock) - [Extended `SignedBeaconBlock`](#extended-signedbeaconblock) - [Extended `BeaconState`](#extended-beaconstate) -- [New containers](#new-containers) +- [New containers](#new-containers-1) - [`ShardBlockWrapper`](#shardblockwrapper) - [`ShardSignableHeader`](#shardsignableheader) - [`ShardState`](#shardstate) @@ -61,6 +69,7 @@ - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) - [`process_attestation`](#process_attestation) + - [New deposits](#new-deposits) - [New Attester slashing processing](#new-attester-slashing-processing) - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index f40e936ce4..80ed544fec 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -17,7 +17,9 @@ - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) + - [New Beacon Chain operations](#new-beacon-chain-operations) - [Helpers](#helpers) + - [`replace_empty_or_append`](#replace_empty_or_append) - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) - [`get_custody_secrets`](#get_custody_secrets) @@ -26,6 +28,8 @@ - [`get_custody_period_for_validator`](#get_custody_period_for_validator) - [Per-block processing](#per-block-processing) - [Custody Game Operations](#custody-game-operations) + - [Chunk challenges](#chunk-challenges) + - [Custody chunk response](#custody-chunk-response) - [Custody key reveals](#custody-key-reveals) - [Early derived secret reveals](#early-derived-secret-reveals) - [Custody Slashings](#custody-slashings) From d30f11a7818eecabcccd6f9126edbf462675eab7 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 1 May 2020 00:16:00 +0100 Subject: [PATCH 013/236] Fix lint --- Makefile | 2 +- specs/phase1/beacon-chain.md | 5 +- specs/phase1/custody-game.md | 17 ++-- .../eth2spec/test/helpers/attestations.py | 32 ++++--- .../pyspec/eth2spec/test/helpers/custody.py | 32 +++---- .../test/helpers/phase1/attestations.py | 6 +- .../test_process_chunk_challenge.py | 85 ++++++++++++++----- .../test_process_custody_slashing.py | 46 ++++------ 8 files changed, 127 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index 8cd7daf58c..18223430ba 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec \ + flake8 --ignore=E252,W504,W503,E128 --max-line-length=120 ./eth2spec \ && cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \ && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1; diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 593f9ba0f5..eda9fdd527 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -291,7 +291,7 @@ class Validator(Container): next_custody_secret_to_reveal: uint64 # TODO: The max_reveal_lateness doesn't really make sense anymore. # So how do we incentivise early custody key reveals now? - all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH + all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH ``` ### Extended `BeaconBlockBody` @@ -978,7 +978,8 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), - next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), get_current_epoch(state)), + next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), + get_current_epoch(state)), all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, )) state.balances.append(amount) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 80ed544fec..9f5a8909bb 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -102,7 +102,8 @@ The following types are defined, mapping into `DomainType` (little endian): ```python def replace_empty_or_append(list: List, new_element: Any) -> int: for i in range(len(list)): - if list[i] == empty(typeof(new_element)): + if list[i] == type(new_element)(): + assert False list[i] = new_element return i list.append(new_element) @@ -236,11 +237,12 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge # Verify the challenge is not a duplicate for record in state.custody_chunk_challenge_records: assert ( - record.data_root != challenge.attestation.data.crosslink.data_root or + record.data_root != data_root or record.chunk_index != challenge.chunk_index ) # Verify depth - transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK + transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] + + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK assert challenge.chunk_index < transition_chunks # Add new chunk challenge record new_record = CustodyChunkChallengeRecord( @@ -264,7 +266,8 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge def process_chunk_challenge_response(state: BeaconState, response: CustodyChunkResponse) -> None: - challenge = next((record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index), None) + challenge = next((record for record in state.custody_chunk_challenge_records if + record.challenge_index == response.challenge_index), None) assert(challenge is not None) # Verify chunk index @@ -417,7 +420,8 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed shard_transition = custody_slashing.shard_transition assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert custody_slashing.data.get_backing().get_left().merkle_root() == shard_transition.shard_data_roots[custody_slashing.data_index] + assert custody_slashing.data.get_backing().get_left().merkle_root() \ + == shard_transition.shard_data_roots[custody_slashing.data_index] assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] # Verify existence and participation of claimed malefactor @@ -467,7 +471,8 @@ Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`: def process_reveal_deadlines(state: BeaconState) -> None: epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): + if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal \ + + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): slash_validator(state, ValidatorIndex(index)) ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 5e536002f5..7f15c00487 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -78,7 +78,8 @@ def build_attestation_data(spec, state, slot, index, shard_transition_root=None) ) -def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None, valid_custody_bits=None): +def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, shard_transition=None, + valid_custody_bits=None): shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) @@ -88,20 +89,14 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, attestation.data.slot, attestation.data.index, ) - current_epoch = spec.get_current_epoch(state) custody_secrets = [None for i in beacon_committee] for i in range(len(beacon_committee)): - validator = state.validators[beacon_committee[i]] - period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch) - epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i]) - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root) - for i, offset_slot in enumerate(offset_slots): attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) @@ -110,7 +105,8 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i]) for j in range(len(attestation.custody_bits_blocks[i])): if attestation.aggregation_bits[j]: - attestation.custody_bits_blocks[i][j] = spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) + attestation.custody_bits_blocks[i][j] = \ + spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) if signed: sign_attestation(spec, state, attestation) @@ -118,7 +114,8 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False, return attestation -def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None, valid_custody_bits=None): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False, + shard_transition=None, valid_custody_bits=None): ''' Construct on-time attestation for next slot ''' @@ -127,7 +124,9 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) + return get_valid_attestation(spec, state, slot=slot, index=index, + signed=signed, on_time=True, shard_transition=shard_transition, + valid_custody_bits=valid_custody_bits) def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None): @@ -139,16 +138,19 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False, shard_transition=shard_transition) + return get_valid_attestation(spec, state, slot=slot, index=index, + signed=signed, on_time=False, shard_transition=shard_transition) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, shard_transition=None, valid_custody_bits=None): +def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True, + shard_transition=None, valid_custody_bits=None): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index, shard_transition_root=hash_tree_root(shard_transition) if shard_transition else spec.Root()) + attestation_data = build_attestation_data(spec, state, slot, index, + shard_transition_root=hash_tree_root(shard_transition) if shard_transition else spec.Root()) beacon_committee = spec.get_beacon_committee( state, @@ -168,7 +170,9 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe sign_attestation(spec, state, attestation) if spec.fork == 'phase1' and on_time: - attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed, shard_transition=shard_transition, valid_custody_bits=valid_custody_bits) + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed, + shard_transition=shard_transition, + valid_custody_bits=valid_custody_bits) return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 2d937a2ee8..898cf7731a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -1,10 +1,7 @@ from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector, ByteList, uint64 -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.utils.merkle_minimal import get_merkle_root, get_merkle_tree, get_merkle_proof -from remerkleable.core import pack_bits_to_chunks -from remerkleable.tree import subtree_fill_to_contents, get_depth, Node, Gindex, gindex_bit_iter, Root +from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, ByteList +from remerkleable.tree import gindex_bit_iter BYTES_PER_CHUNK = 32 @@ -79,9 +76,8 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root) data_index = 0 - data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) - print(hash_tree_root(data)) - print(data.get_backing().get_left().merkle_root()) + data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]( + get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) slashing = spec.CustodySlashing( data_index=data_index, @@ -93,7 +89,7 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval data=data, ) slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING) - slashing_root = spec.compute_signing_root(slashing, domain) + slashing_root = spec.compute_signing_root(slashing, slashing_domain) signed_slashing = spec.SignedCustodySlashing( message=slashing, @@ -103,22 +99,23 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval return signed_slashing -def get_valid_chunk_challenge(spec, state, attestation, shard_transition): - shard = spec.compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) +def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_index=None, chunk_index=None): crosslink_committee = spec.get_beacon_committee( state, attestation.data.slot, attestation.data.index ) responder_index = crosslink_committee[0] - data_index = len(shard_transition.shard_block_lengths) - 1 + data_index = len(shard_transition.shard_block_lengths) - 1 if not data_index else data_index - chunk_count = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + chunk_count = (shard_transition.shard_block_lengths[data_index] + + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK + chunk_index = chunk_count - 1 if not chunk_index else chunk_index return spec.CustodyChunkChallenge( responder_index=responder_index, attestation=attestation, - chunk_index=chunk_count - 1, + chunk_index=chunk_index, data_index=data_index, shard_transition=shard_transition, ) @@ -174,7 +171,8 @@ def get_custody_test_vector(bytelength): def get_shard_transition(spec, start_slot, block_lengths): - b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)).get_backing().get_left().merkle_root() for x in block_lengths] + b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)) + .get_backing().get_left().merkle_root() for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, shard_block_lengths=block_lengths, @@ -183,7 +181,3 @@ def get_shard_transition(spec, start_slot, block_lengths): proposer_signature_aggregate=spec.BLSSignature(), ) return shard_transition - - -def get_custody_merkle_root(data): - return None # get_merkle_tree(chunkify(data))[-1][0] diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py index 061553ef9a..b22a3ea690 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py @@ -1,3 +1,5 @@ +# TODO: What is this file for??? It seems to be broken! +# The phase0 attestations file already adds the custody bit blocks from eth2spec.utils.ssz.ssz_typing import Bitlist from eth2spec.utils import bls @@ -12,16 +14,14 @@ def get_valid_on_time_attestation(spec, state, index=None, signed=False, shard_t if index is None: index = 0 - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False, shard_transition_root=shard_transition_root) + attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) shard = spec.get_shard(state, attestation) offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - print(offset_slots) for _ in offset_slots: attestation.custody_bits_blocks.append( Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) ) - print(len(attestation.custody_bits_blocks)) if signed: sign_attestation(spec, state, attestation) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py index d409b468f5..c71785518d 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -1,15 +1,12 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, - get_custody_test_vector, - get_custody_merkle_root, get_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, ) from eth2spec.test.helpers.state import transition_to -from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -30,7 +27,7 @@ def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=T yield 'custody_chunk_challenge', custody_chunk_challenge if not valid: - expect_assertion_error(lambda: spec.custody_chunk_challenge(state, custody_chunk_challenge)) + expect_assertion_error(lambda: spec.process_chunk_challenge(state, custody_chunk_challenge)) yield 'post', None return @@ -74,12 +71,8 @@ def test_challenge_appended(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) - - transition_chunks = (shard_transition.shard_block_lengths[data_index] + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK - test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[data_index]) - shard_root = get_custody_merkle_root(test_vector) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -92,6 +85,54 @@ def test_challenge_appended(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) +@with_all_phases_except(['phase0']) +@spec_state_test +def test_duplicate_challenge(spec, state): + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + yield from run_chunk_challenge_processing(spec, state, challenge, valid=False) + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_second_challenge(spec, state): + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) + + challenge0 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=0) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge0) + + challenge1 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=1) + + yield from run_chunk_challenge_processing(spec, state, challenge1) + + @with_all_phases_except(['phase0']) @spec_state_test def test_multiple_epochs_custody(spec, state): @@ -100,8 +141,8 @@ def test_multiple_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -122,8 +163,8 @@ def test_many_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -144,8 +185,8 @@ def test_off_chain_attestation(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) @@ -162,8 +203,8 @@ def test_custody_response(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -190,8 +231,8 @@ def test_custody_response_multiple_epochs(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -218,8 +259,8 @@ def test_custody_response_many_epochs(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py index 706fd8f493..087e619f5b 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -1,16 +1,12 @@ from eth2spec.test.helpers.custody import ( get_valid_custody_slashing, - get_custody_test_vector, - get_custody_merkle_root, get_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, ) -from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ByteList -from eth2spec.test.helpers.state import next_epoch, get_balance, transition_to -from eth2spec.test.helpers.block import apply_empty_block +from eth2spec.test.helpers.state import get_balance, transition_to from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -48,7 +44,7 @@ def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, c else: slashed_validator = state.validators[custody_slashing.message.whistleblower_index] assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance - + assert slashed_validator.slashed assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH @@ -63,8 +59,8 @@ def test_custody_slashing(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -72,8 +68,6 @@ def test_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -86,8 +80,8 @@ def test_incorrect_custody_slashing(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=True) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=True) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -95,8 +89,6 @@ def test_incorrect_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=False) @@ -109,8 +101,8 @@ def test_multiple_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -118,8 +110,6 @@ def test_multiple_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -128,12 +118,12 @@ def test_multiple_epochs_custody(spec, state): @with_all_phases_except(['phase0']) @spec_state_test def test_many_epochs_custody(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH* 100) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -141,8 +131,6 @@ def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -155,13 +143,11 @@ def test_off_chain_attestation(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -174,8 +160,8 @@ def test_invalid_custody_slashing(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition, valid_custody_bits=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition, valid_custody_bits=False) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -183,8 +169,6 @@ def test_invalid_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - data_index = 0 - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() From b82496fd11a0d2e8dd7a65700889e365beace13b Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 1 May 2020 00:19:25 +0100 Subject: [PATCH 014/236] Rename file --- ...pdates.py => test_process_custody_final_updates.py} | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) rename tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/{test_process_final_custody_updates.py => test_process_custody_final_updates.py} (96%) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py similarity index 96% rename from tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py rename to tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py index 566d795cfa..396414f2a5 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_final_custody_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py @@ -2,8 +2,6 @@ get_valid_chunk_challenge, get_valid_custody_chunk_response, get_valid_custody_key_reveal, - get_custody_test_vector, - get_custody_merkle_root, get_shard_transition ) from eth2spec.test.helpers.attestations import ( @@ -72,8 +70,8 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -122,8 +120,8 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - data_index = 0 - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) From 964bf42335e9880fcc8eb90056ad53b387e316f3 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 1 May 2020 00:32:02 +0100 Subject: [PATCH 015/236] Fix type --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 9f5a8909bb..c81c6a17b2 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -285,7 +285,7 @@ def process_chunk_challenge_response(state: BeaconState, records[records.index(challenge)] = CustodyChunkChallengeRecord() # Reward the proposer proposer_index = get_beacon_proposer_index(state) - increase_balance(state, proposer_index, get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT) + increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) ``` #### Custody key reveals From f4334d1522bb6f3a34fdb0fe612cf4b7c7b0b760 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 5 May 2020 15:28:12 +0800 Subject: [PATCH 016/236] Delete outdated helper --- .../test/helpers/phase1/attestations.py | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py deleted file mode 100644 index b22a3ea690..0000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ /dev/null @@ -1,65 +0,0 @@ -# TODO: What is this file for??? It seems to be broken! -# The phase0 attestations file already adds the custody bit blocks -from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.utils import bls - -from eth2spec.test.helpers.keys import privkeys -import eth2spec.test.helpers.attestations as phase0_attestations - - -def get_valid_on_time_attestation(spec, state, index=None, signed=False, shard_transition_root=None): - ''' - Construct on-time attestation for next slot - ''' - if index is None: - index = 0 - - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) - shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - - for _ in offset_slots: - attestation.custody_bits_blocks.append( - Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) - ) - - if signed: - sign_attestation(spec, state, attestation) - - return attestation - - -def sign_attestation(spec, state, attestation): - if not any(attestation.custody_bits_blocks): - phase0_attestations.sign_attestation(spec, state, attestation) - return - - committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index) - signatures = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits): - if not abit: - continue - signatures.append(get_attestation_custody_signature( - spec, - state, - attestation.data, - block_index, - cbit, - privkeys[participant] - )) - - attestation.signature = bls.Aggregate(signatures) - - -def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey): - domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = spec.compute_signing_root( - spec.AttestationCustodyBitWrapper( - attestation_data.hash_tree_root(), - block_index, - bit, - ), - domain, - ) - return bls.Sign(privkey, signing_root) From dab5a936c4b1fdfae0cc9c9b5c98ac0643097bd7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 28 Apr 2020 23:55:46 +0800 Subject: [PATCH 017/236] wip shard fork choice rule --- setup.py | 1 + specs/phase1/shard-fork-choice.md | 209 ++++++++++++++++++ .../test/fork_choice/test_get_head.py | 30 +-- .../test/fork_choice/test_on_shard_head.py | 97 ++++++++ .../eth2spec/test/helpers/fork_choice.py | 27 +++ 5 files changed, 335 insertions(+), 29 deletions(-) create mode 100644 specs/phase1/shard-fork-choice.md create mode 100644 tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/fork_choice.py diff --git a/setup.py b/setup.py index e0d6561dd6..316a7d32a2 100644 --- a/setup.py +++ b/setup.py @@ -378,6 +378,7 @@ def finalize_options(self): specs/phase1/shard-transition.md specs/phase1/fork-choice.md specs/phase1/phase1-fork.md + specs/phase1/shard-fork-choice.md """ else: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md new file mode 100644 index 0000000000..46e467e129 --- /dev/null +++ b/specs/phase1/shard-fork-choice.md @@ -0,0 +1,209 @@ +# Ethereum 2.0 Phase 1 -- Beacon Chain + Shard Chain Fork Choice + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Introduction](#introduction) +- [Fork choice](#fork-choice) + - [Helpers](#helpers) + - [Extended `Store`](#extended-store) + - [Updated `get_forkchoice_store`](#updated-get_forkchoice_store) + - [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance) + - [`get_shard_head`](#get_shard_head) + - [`get_shard_ancestor`](#get_shard_ancestor) + - [`filter_shard_block_tree`](#filter_shard_block_tree) + - [`get_filtered_block_tree`](#get_filtered_block_tree) + - [Handlers](#handlers) + - [`on_shard_block`](#on_shard_block) + + + +## Introduction + +This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase 1. + +## Fork choice + +### Helpers + +#### Extended `Store` + +```python +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + best_justified_checkpoint: Checkpoint + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + # shard chain + shard_init_slots: Dict[Shard, Slot] = field(default_factory=dict) + shard_blocks: Dict[Shard, Dict[Root, ShardBlock]] = field(default_factory=dict) + shard_block_states: Dict[Shard, Dict[Root, ShardState]] = field(default_factory=dict) +``` + +#### Updated `get_forkchoice_store` + +```python +def get_forkchoice_store(anchor_state: BeaconState, + shard_init_slots: Dict[Shard, Slot], + anchor_state_shard_blocks: Dict[Shard, Dict[Root, ShardBlock]]) -> Store: + shard_count = len(anchor_state.shard_states) + anchor_block_header = anchor_state.latest_block_header.copy() + if anchor_block_header.state_root == Bytes32(): + anchor_block_header.state_root = hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block_header) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + return Store( + time=anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot, + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + best_justified_checkpoint=justified_checkpoint, + blocks={anchor_root: anchor_block_header}, + block_states={anchor_root: anchor_state.copy()}, + checkpoint_states={justified_checkpoint: anchor_state.copy()}, + # shard chain + shard_init_slots=shard_init_slots, + shard_blocks=anchor_state_shard_blocks, + shard_block_states={ + shard: { + anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard] + } + for shard in map(Shard, range(shard_count)) + }, + ) +``` + +#### `get_shard_latest_attesting_balance` + +```python +def get_shard_latest_attesting_balance(store: Store, shard: Shard, root: Root) -> Gwei: + state = store.checkpoint_states[store.justified_checkpoint] + active_indices = get_active_validator_indices(state, get_current_epoch(state)) + return Gwei(sum( + state.validators[i].effective_balance for i in active_indices + if ( + i in store.latest_messages and get_shard_ancestor( + store, shard, store.latest_messages[i].root, store.shard_blocks[shard][root].slot + ) == root + ) + )) +``` + +#### `get_shard_head` + +```python +def get_shard_head(store: Store, shard: Shard) -> Root: + # Get filtered block tree that only includes viable branches + blocks = get_filtered_shard_block_tree(store, shard) + + # Execute the LMD-GHOST fork choice + head_beacon_root = get_head(store) + head_shard_root = store.block_states[head_beacon_root].shard_states[shard].latest_block_root + while True: + children = [ + root for root in blocks.keys() + if blocks[root].shard_parent_root == head_shard_root + ] + if len(children) == 0: + return head_shard_root + # Sort by latest attesting balance with ties broken lexicographically + head_shard_root = max(children, key=lambda root: (get_shard_latest_attesting_balance(store, shard, root), root)) +``` + +#### `get_shard_ancestor` + +```python +def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Root: + block = store.shard_blocks[shard][root] + if block.slot > slot: + return get_shard_ancestor(store, shard, block.shard_parent_root, slot) + elif block.slot == slot: + return root + else: + # root is older than queried slot, thus a skip slot. Return earliest root prior to slot + return root +``` + +#### `filter_shard_block_tree` + +```python +def filter_shard_block_tree(store: Store, shard: Shard, block_root: Root, blocks: Dict[Root, ShardBlock]) -> bool: + block = store.shard_blocks[shard][block_root] + children = [ + root for root in store.shard_blocks[shard].keys() + if ( + store.shard_blocks[shard][root].shard_parent_root == block_root + and store.shard_blocks[shard][root].slot != store.shard_init_slots[shard] + ) + ] + + if any(children): + filter_block_tree_result = [filter_shard_block_tree(store, shard, child, blocks) for child in children] + if any(filter_block_tree_result): + blocks[block_root] = block + return True + return False + + return False +``` + +#### `get_filtered_block_tree` + +```python +def get_filtered_shard_block_tree(store: Store, shard: Shard) -> Dict[Root, ShardBlock]: + base_beacon_block_root = get_head(store) + base_shard_block_root = store.block_states[base_beacon_block_root].shard_states[shard].latest_block_root + blocks: Dict[Root, ShardBlock] = {} + filter_shard_block_tree(store, shard, base_shard_block_root, blocks) + return blocks +``` + +### Handlers + +#### `on_shard_block` + +```python +def on_shard_block(store: Store, shard: Shard, signed_shard_block: SignedShardBlock) -> None: + shard_block = signed_shard_block.message + + # 1. Check shard parent exists + assert shard_block.shard_parent_root in store.shard_block_states[shard] + pre_shard_state = store.shard_block_states[shard][shard_block.shard_parent_root] + + # 2. Check beacon parent exists + assert shard_block.beacon_parent_root in store.block_states + beacon_state = store.block_states[shard_block.beacon_parent_root] + + # 3. Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert shard_block.slot > finalized_slot + + # 4. Check block is a descendant of the finalized block at the checkpoint finalized slot + assert ( + shard_block.beacon_parent_root == store.finalized_checkpoint.root + or get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root + ) + + # Add new block to the store + store.shard_blocks[shard][hash_tree_root(shard_block)] = shard_block + + # Check the block is valid and compute the post-state + verify_shard_block_message(beacon_state, pre_shard_state, shard_block, shard_block.slot, shard) + verify_shard_block_signature(beacon_state, signed_shard_block) + post_state = get_post_shard_state(beacon_state, pre_shard_state, shard_block) + # Add new state for this block to the store + store.shard_block_states[shard][hash_tree_root(shard_block)] = post_state +``` diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py index 17d4f644f7..e25aad18fc 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py @@ -1,41 +1,13 @@ from eth2spec.test.context import with_all_phases, spec_state_test from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.fork_choice import add_attestation_to_store, add_block_to_store, get_anchor_root from eth2spec.test.helpers.state import ( next_epoch, state_transition_and_sign_block, ) -def add_block_to_store(spec, store, signed_block): - pre_state = store.block_states[signed_block.message.parent_root] - block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT - - if store.time < block_time: - spec.on_tick(store, block_time) - - spec.on_block(store, signed_block) - - -def add_attestation_to_store(spec, store, attestation): - parent_block = store.blocks[attestation.data.beacon_block_root] - pre_state = store.block_states[spec.hash_tree_root(parent_block)] - block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT - next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT - - if store.time < next_epoch_time: - spec.on_tick(store, next_epoch_time) - - spec.on_attestation(store, attestation) - - -def get_anchor_root(spec, state): - anchor_block_header = state.latest_block_header.copy() - if anchor_block_header.state_root == spec.Bytes32(): - anchor_block_header.state_root = spec.hash_tree_root(state) - return spec.hash_tree_root(anchor_block_header) - - @with_all_phases @spec_state_test def test_genesis(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py new file mode 100644 index 0000000000..8e72e214ed --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -0,0 +1,97 @@ +from eth2spec.utils.ssz.ssz_impl import hash_tree_root + +from eth2spec.test.context import spec_state_test, with_all_phases_except, PHASE0 +from eth2spec.test.helpers.shard_block import ( + build_attestation_with_shard_transition, + build_shard_block, + build_shard_transitions_till_slot, +) +from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root +from eth2spec.test.helpers.state import next_slot, state_transition_and_sign_block +from eth2spec.test.helpers.block import build_empty_block + + +def run_on_shard_block(spec, store, shard, signed_block, valid=True): + if not valid: + try: + spec.on_shard_block(store, shard, signed_block) + except AssertionError: + return + else: + assert False + + spec.on_shard_block(store, shard, signed_block) + assert store.shard_blocks[shard][hash_tree_root(signed_block.message)] == signed_block.message + + +def run_apply_shard_and_beacon(spec, state, store, shard, committee_index): + store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + + # Create SignedShardBlock + body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, signed=True) + shard_blocks = [shard_block] + + # Attester creates `attestation` + # Use temporary next state to get ShardTransition of shard block + shard_transitions = build_shard_transitions_till_slot( + spec, + state, + shards=[shard, ], + shard_blocks={shard: shard_blocks}, + target_len_offset_slot=1, + ) + shard_transition = shard_transitions[shard] + attestation = build_attestation_with_shard_transition( + spec, + state, + slot=state.slot, + index=committee_index, + target_len_offset_slot=1, + shard_transition=shard_transition, + ) + + # Propose beacon block at slot + beacon_block = build_empty_block(spec, state, slot=state.slot + 1) + beacon_block.body.attestations = [attestation] + beacon_block.body.shard_transitions = shard_transitions + signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) + + run_on_shard_block(spec, store, shard, shard_block) + add_block_to_store(spec, store, signed_beacon_block) + + assert spec.get_head(store) == beacon_block.hash_tree_root() + assert spec.get_shard_head(store, shard) == shard_block.message.hash_tree_root() + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_basic(spec, state): + spec.PHASE_1_GENESIS_SLOT = 0 # FIXME: remove mocking + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + # Initialization + shard_count = len(state.shard_states) + # Genesis shard blocks + anchor_shard_blocks = { + shard: { + state.shard_states[shard].latest_block_root: spec.ShardBlock( + slot=state.slot, + ) + } + for shard in map(spec.Shard, range(shard_count)) + } + shard_init_slots = { + shard: state.slot + for shard in map(spec.Shard, range(shard_count)) + } + store = spec.get_forkchoice_store(state, shard_init_slots, anchor_shard_blocks) + anchor_root = get_anchor_root(spec, state) + assert spec.get_head(store) == anchor_root + + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + + run_apply_shard_and_beacon(spec, state, store, shard, committee_index) + run_apply_shard_and_beacon(spec, state, store, shard, committee_index) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py new file mode 100644 index 0000000000..04e36ea849 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -0,0 +1,27 @@ +def get_anchor_root(spec, state): + anchor_block_header = state.latest_block_header.copy() + if anchor_block_header.state_root == spec.Bytes32(): + anchor_block_header.state_root = spec.hash_tree_root(state) + return spec.hash_tree_root(anchor_block_header) + + +def add_block_to_store(spec, store, signed_block): + pre_state = store.block_states[signed_block.message.parent_root] + block_time = pre_state.genesis_time + signed_block.message.slot * spec.SECONDS_PER_SLOT + + if store.time < block_time: + spec.on_tick(store, block_time) + + spec.on_block(store, signed_block) + + +def add_attestation_to_store(spec, store, attestation): + parent_block = store.blocks[attestation.data.beacon_block_root] + pre_state = store.block_states[spec.hash_tree_root(parent_block)] + block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT + next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT + + if store.time < next_epoch_time: + spec.on_tick(store, next_epoch_time) + + spec.on_attestation(store, attestation) From cddf9cf114744c07d8a1ce455c5cf73ff5ad89db Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 30 Apr 2020 20:06:20 +0800 Subject: [PATCH 018/236] Refactor --- specs/phase1/shard-fork-choice.md | 56 ++++++++++--------- .../test/fork_choice/test_on_shard_head.py | 18 +----- 2 files changed, 31 insertions(+), 43 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 46e467e129..e9026b9914 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -35,7 +35,13 @@ This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase ```python @dataclass -class Store(object): +class Store: + + @dataclass + class ShardStore: + blocks: Dict[Root, ShardBlock] = field(default_factory=dict) + block_states: Dict[Root, ShardState] = field(default_factory=dict) + time: uint64 genesis_time: uint64 justified_checkpoint: Checkpoint @@ -46,17 +52,13 @@ class Store(object): checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) # shard chain - shard_init_slots: Dict[Shard, Slot] = field(default_factory=dict) - shard_blocks: Dict[Shard, Dict[Root, ShardBlock]] = field(default_factory=dict) - shard_block_states: Dict[Shard, Dict[Root, ShardState]] = field(default_factory=dict) + shards: Dict[Shard, ShardStore] = field(default_factory=dict) # noqa: F821 ``` #### Updated `get_forkchoice_store` ```python -def get_forkchoice_store(anchor_state: BeaconState, - shard_init_slots: Dict[Shard, Slot], - anchor_state_shard_blocks: Dict[Shard, Dict[Root, ShardBlock]]) -> Store: +def get_forkchoice_store(anchor_state: BeaconState) -> Store: shard_count = len(anchor_state.shard_states) anchor_block_header = anchor_state.latest_block_header.copy() if anchor_block_header.state_root == Bytes32(): @@ -65,6 +67,14 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_epoch = get_current_epoch(anchor_state) justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + + shard_stores = {} + for shard in map(Shard, range(shard_count)): + shard_stores[shard] = Store.ShardStore( + blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot)}, + block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]}, + ) + return Store( time=anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot, genesis_time=anchor_state.genesis_time, @@ -75,14 +85,7 @@ def get_forkchoice_store(anchor_state: BeaconState, block_states={anchor_root: anchor_state.copy()}, checkpoint_states={justified_checkpoint: anchor_state.copy()}, # shard chain - shard_init_slots=shard_init_slots, - shard_blocks=anchor_state_shard_blocks, - shard_block_states={ - shard: { - anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard] - } - for shard in map(Shard, range(shard_count)) - }, + shards=shard_stores, ) ``` @@ -96,7 +99,7 @@ def get_shard_latest_attesting_balance(store: Store, shard: Shard, root: Root) - state.validators[i].effective_balance for i in active_indices if ( i in store.latest_messages and get_shard_ancestor( - store, shard, store.latest_messages[i].root, store.shard_blocks[shard][root].slot + store, shard, store.latest_messages[i].root, store.shards[shard].blocks[root].slot ) == root ) )) @@ -127,7 +130,7 @@ def get_shard_head(store: Store, shard: Shard) -> Root: ```python def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Root: - block = store.shard_blocks[shard][root] + block = store.shards[shard].blocks[root] if block.slot > slot: return get_shard_ancestor(store, shard, block.shard_parent_root, slot) elif block.slot == slot: @@ -141,13 +144,11 @@ def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Ro ```python def filter_shard_block_tree(store: Store, shard: Shard, block_root: Root, blocks: Dict[Root, ShardBlock]) -> bool: - block = store.shard_blocks[shard][block_root] + shard_store = store.shards[shard] + block = shard_store.blocks[block_root] children = [ - root for root in store.shard_blocks[shard].keys() - if ( - store.shard_blocks[shard][root].shard_parent_root == block_root - and store.shard_blocks[shard][root].slot != store.shard_init_slots[shard] - ) + root for root in shard_store.blocks.keys() + if shard_store.blocks[root].shard_parent_root == block_root ] if any(children): @@ -178,10 +179,11 @@ def get_filtered_shard_block_tree(store: Store, shard: Shard) -> Dict[Root, Shar ```python def on_shard_block(store: Store, shard: Shard, signed_shard_block: SignedShardBlock) -> None: shard_block = signed_shard_block.message + shard_store = store.shards[shard] # 1. Check shard parent exists - assert shard_block.shard_parent_root in store.shard_block_states[shard] - pre_shard_state = store.shard_block_states[shard][shard_block.shard_parent_root] + assert shard_block.shard_parent_root in shard_store.block_states + pre_shard_state = shard_store.block_states[shard_block.shard_parent_root] # 2. Check beacon parent exists assert shard_block.beacon_parent_root in store.block_states @@ -198,12 +200,12 @@ def on_shard_block(store: Store, shard: Shard, signed_shard_block: SignedShardBl ) # Add new block to the store - store.shard_blocks[shard][hash_tree_root(shard_block)] = shard_block + shard_store.blocks[hash_tree_root(shard_block)] = shard_block # Check the block is valid and compute the post-state verify_shard_block_message(beacon_state, pre_shard_state, shard_block, shard_block.slot, shard) verify_shard_block_signature(beacon_state, signed_shard_block) post_state = get_post_shard_state(beacon_state, pre_shard_state, shard_block) # Add new state for this block to the store - store.shard_block_states[shard][hash_tree_root(shard_block)] = post_state + shard_store.block_states[hash_tree_root(shard_block)] = post_state ``` diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 8e72e214ed..220c510e7a 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -21,7 +21,7 @@ def run_on_shard_block(spec, store, shard, signed_block, valid=True): assert False spec.on_shard_block(store, shard, signed_block) - assert store.shard_blocks[shard][hash_tree_root(signed_block.message)] == signed_block.message + assert store.shards[shard].blocks[hash_tree_root(signed_block.message)] == signed_block.message def run_apply_shard_and_beacon(spec, state, store, shard, committee_index): @@ -72,21 +72,7 @@ def test_basic(spec, state): next_slot(spec, state) # Initialization - shard_count = len(state.shard_states) - # Genesis shard blocks - anchor_shard_blocks = { - shard: { - state.shard_states[shard].latest_block_root: spec.ShardBlock( - slot=state.slot, - ) - } - for shard in map(spec.Shard, range(shard_count)) - } - shard_init_slots = { - shard: state.slot - for shard in map(spec.Shard, range(shard_count)) - } - store = spec.get_forkchoice_store(state, shard_init_slots, anchor_shard_blocks) + store = spec.get_forkchoice_store(state) anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root From 8fafb6a9e5b17ee3e93fa0c1b3a8795608f7336b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 1 May 2020 10:25:58 +0800 Subject: [PATCH 019/236] Make `ShardStore` an independent object --- specs/phase1/shard-fork-choice.md | 97 ++++++------------- .../test/fork_choice/test_on_shard_head.py | 20 ++-- 2 files changed, 43 insertions(+), 74 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index e9026b9914..4b3f42194e 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -31,75 +31,38 @@ This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase ### Helpers -#### Extended `Store` +#### `ShardStore` ```python @dataclass -class Store: - - @dataclass - class ShardStore: - blocks: Dict[Root, ShardBlock] = field(default_factory=dict) - block_states: Dict[Root, ShardState] = field(default_factory=dict) - - time: uint64 - genesis_time: uint64 - justified_checkpoint: Checkpoint - finalized_checkpoint: Checkpoint - best_justified_checkpoint: Checkpoint - blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) - block_states: Dict[Root, BeaconState] = field(default_factory=dict) - checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) - latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) - # shard chain - shards: Dict[Shard, ShardStore] = field(default_factory=dict) # noqa: F821 +class ShardStore: + shard: Shard + blocks: Dict[Root, ShardBlock] = field(default_factory=dict) + block_states: Dict[Root, ShardState] = field(default_factory=dict) ``` -#### Updated `get_forkchoice_store` +#### Updated `get_forkchoice_shard_store` ```python -def get_forkchoice_store(anchor_state: BeaconState) -> Store: - shard_count = len(anchor_state.shard_states) - anchor_block_header = anchor_state.latest_block_header.copy() - if anchor_block_header.state_root == Bytes32(): - anchor_block_header.state_root = hash_tree_root(anchor_state) - anchor_root = hash_tree_root(anchor_block_header) - anchor_epoch = get_current_epoch(anchor_state) - justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) - - shard_stores = {} - for shard in map(Shard, range(shard_count)): - shard_stores[shard] = Store.ShardStore( - blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot)}, - block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]}, - ) - - return Store( - time=anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot, - genesis_time=anchor_state.genesis_time, - justified_checkpoint=justified_checkpoint, - finalized_checkpoint=finalized_checkpoint, - best_justified_checkpoint=justified_checkpoint, - blocks={anchor_root: anchor_block_header}, - block_states={anchor_root: anchor_state.copy()}, - checkpoint_states={justified_checkpoint: anchor_state.copy()}, - # shard chain - shards=shard_stores, +def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore: + return ShardStore( + shard=shard, + blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot)}, + block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]}, ) ``` #### `get_shard_latest_attesting_balance` ```python -def get_shard_latest_attesting_balance(store: Store, shard: Shard, root: Root) -> Gwei: +def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, root: Root) -> Gwei: state = store.checkpoint_states[store.justified_checkpoint] active_indices = get_active_validator_indices(state, get_current_epoch(state)) return Gwei(sum( state.validators[i].effective_balance for i in active_indices if ( i in store.latest_messages and get_shard_ancestor( - store, shard, store.latest_messages[i].root, store.shards[shard].blocks[root].slot + store, shard_store, store.latest_messages[i].root, shard_store.blocks[root].slot ) == root ) )) @@ -108,13 +71,13 @@ def get_shard_latest_attesting_balance(store: Store, shard: Shard, root: Root) - #### `get_shard_head` ```python -def get_shard_head(store: Store, shard: Shard) -> Root: +def get_shard_head(store: Store, shard_store: ShardStore) -> Root: # Get filtered block tree that only includes viable branches - blocks = get_filtered_shard_block_tree(store, shard) + blocks = get_filtered_shard_block_tree(store, shard_store) # Execute the LMD-GHOST fork choice head_beacon_root = get_head(store) - head_shard_root = store.block_states[head_beacon_root].shard_states[shard].latest_block_root + head_shard_root = store.block_states[head_beacon_root].shard_states[shard_store.shard].latest_block_root while True: children = [ root for root in blocks.keys() @@ -123,16 +86,18 @@ def get_shard_head(store: Store, shard: Shard) -> Root: if len(children) == 0: return head_shard_root # Sort by latest attesting balance with ties broken lexicographically - head_shard_root = max(children, key=lambda root: (get_shard_latest_attesting_balance(store, shard, root), root)) + head_shard_root = max( + children, key=lambda root: (get_shard_latest_attesting_balance(store, shard_store, root), root) + ) ``` #### `get_shard_ancestor` ```python -def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Root: - block = store.shards[shard].blocks[root] +def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: Slot) -> Root: + block = shard_store.blocks[root] if block.slot > slot: - return get_shard_ancestor(store, shard, block.shard_parent_root, slot) + return get_shard_ancestor(store, shard_store, block.shard_parent_root, slot) elif block.slot == slot: return root else: @@ -143,8 +108,10 @@ def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Ro #### `filter_shard_block_tree` ```python -def filter_shard_block_tree(store: Store, shard: Shard, block_root: Root, blocks: Dict[Root, ShardBlock]) -> bool: - shard_store = store.shards[shard] +def filter_shard_block_tree(store: Store, + shard_store: ShardStore, + block_root: Root, + blocks: Dict[Root, ShardBlock]) -> bool: block = shard_store.blocks[block_root] children = [ root for root in shard_store.blocks.keys() @@ -152,7 +119,7 @@ def filter_shard_block_tree(store: Store, shard: Shard, block_root: Root, blocks ] if any(children): - filter_block_tree_result = [filter_shard_block_tree(store, shard, child, blocks) for child in children] + filter_block_tree_result = [filter_shard_block_tree(store, shard_store, child, blocks) for child in children] if any(filter_block_tree_result): blocks[block_root] = block return True @@ -164,11 +131,12 @@ def filter_shard_block_tree(store: Store, shard: Shard, block_root: Root, blocks #### `get_filtered_block_tree` ```python -def get_filtered_shard_block_tree(store: Store, shard: Shard) -> Dict[Root, ShardBlock]: +def get_filtered_shard_block_tree(store: Store, shard_store: ShardStore) -> Dict[Root, ShardBlock]: + shard = shard_store.shard base_beacon_block_root = get_head(store) base_shard_block_root = store.block_states[base_beacon_block_root].shard_states[shard].latest_block_root blocks: Dict[Root, ShardBlock] = {} - filter_shard_block_tree(store, shard, base_shard_block_root, blocks) + filter_shard_block_tree(store, shard_store, base_shard_block_root, blocks) return blocks ``` @@ -177,10 +145,9 @@ def get_filtered_shard_block_tree(store: Store, shard: Shard) -> Dict[Root, Shar #### `on_shard_block` ```python -def on_shard_block(store: Store, shard: Shard, signed_shard_block: SignedShardBlock) -> None: +def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: SignedShardBlock) -> None: shard_block = signed_shard_block.message - shard_store = store.shards[shard] - + shard = shard_store.shard # 1. Check shard parent exists assert shard_block.shard_parent_root in shard_store.block_states pre_shard_state = shard_store.block_states[shard_block.shard_parent_root] diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 220c510e7a..f4b883f06c 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -11,20 +11,21 @@ from eth2spec.test.helpers.block import build_empty_block -def run_on_shard_block(spec, store, shard, signed_block, valid=True): +def run_on_shard_block(spec, store, shard_store, signed_block, valid=True): if not valid: try: - spec.on_shard_block(store, shard, signed_block) + spec.on_shard_block(store, shard_store, signed_block) except AssertionError: return else: assert False - spec.on_shard_block(store, shard, signed_block) - assert store.shards[shard].blocks[hash_tree_root(signed_block.message)] == signed_block.message + spec.on_shard_block(store, shard_store, signed_block) + assert shard_store.blocks[hash_tree_root(signed_block.message)] == signed_block.message -def run_apply_shard_and_beacon(spec, state, store, shard, committee_index): +def run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index): + shard = shard_store.shard store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH # Create SignedShardBlock @@ -57,11 +58,11 @@ def run_apply_shard_and_beacon(spec, state, store, shard, committee_index): beacon_block.body.shard_transitions = shard_transitions signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) - run_on_shard_block(spec, store, shard, shard_block) + run_on_shard_block(spec, store, shard_store, shard_block) add_block_to_store(spec, store, signed_beacon_block) assert spec.get_head(store) == beacon_block.hash_tree_root() - assert spec.get_shard_head(store, shard) == shard_block.message.hash_tree_root() + assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root() @with_all_phases_except([PHASE0]) @@ -78,6 +79,7 @@ def test_basic(spec, state): committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard_store = spec.get_forkchoice_shard_store(state, shard) - run_apply_shard_and_beacon(spec, state, store, shard, committee_index) - run_apply_shard_and_beacon(spec, state, store, shard, committee_index) + run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) + run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) From fca1bbccb948b132fef22a9732c2f0a6aef0b2a4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 1 May 2020 10:33:23 +0800 Subject: [PATCH 020/236] Remove `get_filtered_shard_block_tree` --- specs/phase1/shard-fork-choice.md | 50 ++++--------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 4b3f42194e..b026aabfc9 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -11,13 +11,11 @@ - [Introduction](#introduction) - [Fork choice](#fork-choice) - [Helpers](#helpers) - - [Extended `Store`](#extended-store) - - [Updated `get_forkchoice_store`](#updated-get_forkchoice_store) + - [`ShardStore`](#shardstore) + - [`get_forkchoice_shard_store`](#get_forkchoice_shard_store) - [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance) - [`get_shard_head`](#get_shard_head) - [`get_shard_ancestor`](#get_shard_ancestor) - - [`filter_shard_block_tree`](#filter_shard_block_tree) - - [`get_filtered_block_tree`](#get_filtered_block_tree) - [Handlers](#handlers) - [`on_shard_block`](#on_shard_block) @@ -41,7 +39,7 @@ class ShardStore: block_states: Dict[Root, ShardState] = field(default_factory=dict) ``` -#### Updated `get_forkchoice_shard_store` +#### `get_forkchoice_shard_store` ```python def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore: @@ -72,16 +70,13 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro ```python def get_shard_head(store: Store, shard_store: ShardStore) -> Root: - # Get filtered block tree that only includes viable branches - blocks = get_filtered_shard_block_tree(store, shard_store) - # Execute the LMD-GHOST fork choice head_beacon_root = get_head(store) head_shard_root = store.block_states[head_beacon_root].shard_states[shard_store.shard].latest_block_root while True: children = [ - root for root in blocks.keys() - if blocks[root].shard_parent_root == head_shard_root + root for root in shard_store.blocks.keys() + if shard_store.blocks[root].shard_parent_root == head_shard_root ] if len(children) == 0: return head_shard_root @@ -105,41 +100,6 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: return root ``` -#### `filter_shard_block_tree` - -```python -def filter_shard_block_tree(store: Store, - shard_store: ShardStore, - block_root: Root, - blocks: Dict[Root, ShardBlock]) -> bool: - block = shard_store.blocks[block_root] - children = [ - root for root in shard_store.blocks.keys() - if shard_store.blocks[root].shard_parent_root == block_root - ] - - if any(children): - filter_block_tree_result = [filter_shard_block_tree(store, shard_store, child, blocks) for child in children] - if any(filter_block_tree_result): - blocks[block_root] = block - return True - return False - - return False -``` - -#### `get_filtered_block_tree` - -```python -def get_filtered_shard_block_tree(store: Store, shard_store: ShardStore) -> Dict[Root, ShardBlock]: - shard = shard_store.shard - base_beacon_block_root = get_head(store) - base_shard_block_root = store.block_states[base_beacon_block_root].shard_states[shard].latest_block_root - blocks: Dict[Root, ShardBlock] = {} - filter_shard_block_tree(store, shard_store, base_shard_block_root, blocks) - return blocks -``` - ### Handlers #### `on_shard_block` From 79b1b4bdbe0c4822d4a2e197a7bf59930ffe69c1 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 1 May 2020 10:52:45 +0800 Subject: [PATCH 021/236] Add `(shard, shard_root)` to `LatestMessage` --- specs/phase1/fork-choice.md | 32 ++++++++++++++++++- .../test/fork_choice/test_on_attestation.py | 13 ++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md index d8bf7fa090..f4e771ddb4 100644 --- a/specs/phase1/fork-choice.md +++ b/specs/phase1/fork-choice.md @@ -10,6 +10,9 @@ - [Introduction](#introduction) - [Fork choice](#fork-choice) + - [Helpers](#helpers) + - [Extended `LatestMessage`](#extended-latestmessage) + - [Updated `update_latest_messages`](#updated-update_latest_messages) - [Handlers](#handlers) @@ -25,6 +28,33 @@ Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_atte The rest of the fork choice remains stable. +### Helpers + +#### Extended `LatestMessage` + +```python +@dataclass(eq=True, frozen=True) +class LatestMessage(object): + epoch: Epoch + root: Root + shard: Shard + shard_root: Root +``` + +#### Updated `update_latest_messages` + +```python +def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: + target = attestation.data.target + beacon_block_root = attestation.data.beacon_block_root + shard = get_shard(store.block_states[beacon_block_root], attestation) + for i in attesting_indices: + if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: + store.latest_messages[i] = LatestMessage( + epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.head_shard_root + ) +``` + ### Handlers ```python @@ -49,4 +79,4 @@ def on_attestation(store: Store, attestation: Attestation) -> None: if attestation.aggregation_bits[i] ] update_latest_messages(store, attesting_indices, attestation) -``` \ No newline at end of file +``` diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index 360c18ccd1..1bce42ca35 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -18,18 +18,25 @@ def run_on_attestation(spec, state, store, attestation, valid=True): if spec.fork == PHASE0: sample_index = indexed_attestation.attesting_indices[0] + latest_message = spec.LatestMessage( + epoch=attestation.data.target.epoch, + root=attestation.data.beacon_block_root, + ) else: attesting_indices = [ index for i, index in enumerate(indexed_attestation.committee) if attestation.aggregation_bits[i] ] sample_index = attesting_indices[0] - assert ( - store.latest_messages[sample_index] == - spec.LatestMessage( + latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, + shard=spec.get_shard(state, attestation), + shard_root=attestation.data.head_shard_root, ) + + assert ( + store.latest_messages[sample_index] == latest_message ) From 3851a26a0fa1e8c860956ba4da893608a3126752 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 07:33:07 -0600 Subject: [PATCH 022/236] add phase 1 custody objects to custody-game.md --- specs/phase1/beacon-chain.md | 99 +----------------------------------- specs/phase1/custody-game.md | 92 +++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 98 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 278efda28f..27a9229ddd 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -12,14 +12,6 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) -- [New containers](#new-containers) - - [`CustodyChunkChallenge`](#custodychunkchallenge) - - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) - - [`CustodyChunkResponse`](#custodychunkresponse) - - [`CustodySlashing`](#custodyslashing) - - [`SignedCustodySlashing`](#signedcustodyslashing) - - [`CustodyKeyReveal`](#custodykeyreveal) - - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) @@ -31,7 +23,7 @@ - [Extended `BeaconBlock`](#extended-beaconblock) - [Extended `SignedBeaconBlock`](#extended-signedbeaconblock) - [Extended `BeaconState`](#extended-beaconstate) -- [New containers](#new-containers-1) +- [New containers](#new-containers) - [`ShardBlock`](#shardblock) - [`SignedShardBlock`](#signedshardblock) - [`ShardBlockHeader`](#shardblockheader) @@ -124,95 +116,6 @@ Configuration is not namespaced. Instead it is strictly an extension; | `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes | | `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) | - | - | - - -## New containers - -### `CustodyChunkChallenge` - -```python -class CustodyChunkChallenge(Container): - responder_index: ValidatorIndex - shard_transition: ShardTransition - attestation: Attestation - data_index: uint64 - chunk_index: uint64 -``` - -### `CustodyChunkChallengeRecord` - -```python -class CustodyChunkChallengeRecord(Container): - challenge_index: uint64 - challenger_index: ValidatorIndex - responder_index: ValidatorIndex - inclusion_epoch: Epoch - data_root: Root - chunk_index: uint64 -``` - -#### `CustodyChunkResponse` - -```python -class CustodyChunkResponse(Container): - challenge_index: uint64 - chunk_index: uint64 - chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] - branch: Vector[Root, CUSTODY_RESPONSE_DEPTH] -``` - -#### `CustodySlashing` - -```python -class CustodySlashing(Container): - # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. - # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. - data_index: uint64 - malefactor_index: ValidatorIndex - malefactor_secret: BLSSignature - whistleblower_index: ValidatorIndex - shard_transition: ShardTransition - attestation: Attestation - data: ByteList[MAX_SHARD_BLOCK_SIZE] -``` - -#### `SignedCustodySlashing` - -```python -class SignedCustodySlashing(Container): - message: CustodySlashing - signature: BLSSignature -``` - - -#### `CustodyKeyReveal` - -```python -class CustodyKeyReveal(Container): - # Index of the validator whose key is being revealed - revealer_index: ValidatorIndex - # Reveal (masked signature) - reveal: BLSSignature -``` - -#### `EarlyDerivedSecretReveal` - -Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). - -```python -class EarlyDerivedSecretReveal(Container): - # Index of the validator whose key is being revealed - revealed_index: ValidatorIndex - # RANDAO epoch of the key that is being revealed - epoch: Epoch - # Reveal (masked signature) - reveal: BLSSignature - # Index of the validator who revealed (whistleblower) - masker_index: ValidatorIndex - # Mask used to hide the actual reveal signature (prevent reveal from being stolen) - mask: Bytes32 -``` - ## Updated containers The following containers have updated definitions in Phase 1. diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index c81c6a17b2..96a7f56161 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -18,6 +18,13 @@ - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) - [New Beacon Chain operations](#new-beacon-chain-operations) + - [`CustodyChunkChallenge`](#custodychunkchallenge) + - [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord) + - [`CustodyChunkResponse`](#custodychunkresponse) + - [`CustodySlashing`](#custodyslashing) + - [`SignedCustodySlashing`](#signedcustodyslashing) + - [`CustodyKeyReveal`](#custodykeyreveal) + - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) - [`replace_empty_or_append`](#replace_empty_or_append) - [`legendre_bit`](#legendre_bit) @@ -95,6 +102,91 @@ The following types are defined, mapping into `DomainType` (little endian): ### New Beacon Chain operations +#### `CustodyChunkChallenge` + +```python +class CustodyChunkChallenge(Container): + responder_index: ValidatorIndex + shard_transition: ShardTransition + attestation: Attestation + data_index: uint64 + chunk_index: uint64 +``` + +#### `CustodyChunkChallengeRecord` + +```python +class CustodyChunkChallengeRecord(Container): + challenge_index: uint64 + challenger_index: ValidatorIndex + responder_index: ValidatorIndex + inclusion_epoch: Epoch + data_root: Root + chunk_index: uint64 +``` + +#### `CustodyChunkResponse` + +```python +class CustodyChunkResponse(Container): + challenge_index: uint64 + chunk_index: uint64 + chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK] + branch: Vector[Root, CUSTODY_RESPONSE_DEPTH] +``` + +#### `CustodySlashing` + +```python +class CustodySlashing(Container): + # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. + # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. + data_index: uint64 + malefactor_index: ValidatorIndex + malefactor_secret: BLSSignature + whistleblower_index: ValidatorIndex + shard_transition: ShardTransition + attestation: Attestation + data: ByteList[MAX_SHARD_BLOCK_SIZE] +``` + +#### `SignedCustodySlashing` + +```python +class SignedCustodySlashing(Container): + message: CustodySlashing + signature: BLSSignature +``` + +#### `CustodyKeyReveal` + +```python +class CustodyKeyReveal(Container): + # Index of the validator whose key is being revealed + revealer_index: ValidatorIndex + # Reveal (masked signature) + reveal: BLSSignature +``` + +#### `EarlyDerivedSecretReveal` + +Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). + +```python +class EarlyDerivedSecretReveal(Container): + # Index of the validator whose key is being revealed + revealed_index: ValidatorIndex + # RANDAO epoch of the key that is being revealed + epoch: Epoch + # Reveal (masked signature) + reveal: BLSSignature + # Index of the validator who revealed (whistleblower) + masker_index: ValidatorIndex + # Mask used to hide the actual reveal signature (prevent reveal from being stolen) + mask: Bytes32 +``` + + ## Helpers ### `replace_empty_or_append` From 3f0e58a8ed52253d84af70a1e041d64648dbe19b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 07:50:05 -0600 Subject: [PATCH 023/236] add chunk challenge and response to block and operations --- configs/mainnet.yaml | 2 ++ configs/minimal.yaml | 2 ++ specs/phase1/beacon-chain.md | 4 +++- specs/phase1/custody-game.md | 7 +++---- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index f5f38de5eb..6475d491e5 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -214,6 +214,8 @@ MAX_REVEAL_LATENESS_DECREMENT: 128 # 2**8 (= 256) MAX_CUSTODY_KEY_REVEALS: 256 MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_CHUNK_CHALLENGES: 4 +MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 16 MAX_CUSTODY_SLASHINGS: 1 # Reward and penalty quotients diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 1b5aac5f68..89e2a7eae9 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -215,6 +215,8 @@ CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 # 2**8 (= 256) MAX_CUSTODY_KEY_REVEALS: 256 MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_CHUNK_CHALLENGES: 2 +MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 8 MAX_CUSTODY_SLASHINGS: 1 # Reward and penalty quotients diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 27a9229ddd..e48c99c4a0 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -215,9 +215,11 @@ class BeaconBlockBody(Container): deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] # Custody game - custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] + chunk_challenges: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGES] + chunk_challenge_responses: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES] custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS] early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS] + custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] # Shards shard_transitions: Vector[ShardTransition, MAX_SHARDS] # Light clients diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 96a7f56161..08893ef532 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -81,6 +81,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) | | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `1` | | `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `2**4` (= 16) | | `MAX_CUSTODY_SLASHINGS` | `1` | ### Reward and penalty quotients @@ -297,6 +298,8 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - for operation in operations: fn(state, operation) + for_ops(body.chunk_challenges, process_chunk_challenge) + for_ops(body.chunk_challenge_responses, process_chunk_challenge) for_ops(body.custody_key_reveals, process_custody_key_reveal) for_ops(body.early_derived_secret_reveals, process_early_derived_secret_reveal) for_ops(body.custody_slashings, process_custody_slashing) @@ -304,10 +307,6 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - #### 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 From 7fc9dbf297e4900aa4345443722e3c184790a557 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 07:52:26 -0600 Subject: [PATCH 024/236] clarify comment for ShardTransition.shard_data_roots --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index e48c99c4a0..0f5e26a8cd 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -352,7 +352,7 @@ class ShardTransition(Container): # Shard block lengths shard_block_lengths: List[uint64, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Shard data roots - # The root is of ByteVector[MAX_] + # The root is of ByteList[MAX_SHARD_BLOCK_SIZE] shard_data_roots: List[Bytes32, MAX_SHARD_BLOCKS_PER_ATTESTATION] # Intermediate shard states shard_states: List[ShardState, MAX_SHARD_BLOCKS_PER_ATTESTATION] From 1623086088e6f0496566ab7d50d16a8c78cdebf0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 08:14:04 -0600 Subject: [PATCH 025/236] make get_validator_from_deposit for better code reuse across phase 0 and 1 --- specs/phase0/beacon-chain.md | 26 +++++++++----- specs/phase1/beacon-chain.md | 66 ++++++++++-------------------------- 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 3ef4081ce0..ada599c9d4 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1632,6 +1632,22 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ##### Deposits +```python +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) +``` + ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Verify the Merkle branch @@ -1662,15 +1678,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: return # Add validator and balance entries - state.validators.append(Validator( - pubkey=pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), - )) + state.validators.append(get_validator_from_deposit(state, deposit)) state.balances.append(amount) else: # Increase balance by deposit amount diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0f5e26a8cd..32e9c925ba 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -883,58 +883,28 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: state.previous_epoch_attestations.append(pending_attestation) ``` -##### New deposits +##### New default validator for deposits ```python -def process_deposit(state: BeaconState, deposit: Deposit) -> None: - # Verify the Merkle branch - 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 - index=state.eth1_deposit_index, - root=state.eth1_data.deposit_root, +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + next_custody_secret_to_reveal = get_custody_period_for_validator( + ValidatorIndex(len(state.validators)), + get_current_epoch(state), ) - # Deposits must be processed in order - state.eth1_deposit_index += 1 - - pubkey = deposit.data.pubkey - 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) 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, - ) - 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 - - # Add validator and balance entries - # TODO: This function is duplicated from phase 1 just because the validator definition - # has changed and we need to initialize it properly. Is there a better solution for - # this? - state.validators.append(Validator( - pubkey=pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE), - next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(len(state.validators)), - get_current_epoch(state)), - all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, - )) - state.balances.append(amount) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + next_custody_secret_to_reveal=next_custody_secret_to_reveal, + all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH, + ) ``` ##### New Attester slashing processing From 78947548e6b5e31e7601f3519fa801515c4e84a0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 08:31:01 -0600 Subject: [PATCH 026/236] add process_challenge_deadlines to process_epoch --- specs/phase1/beacon-chain.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 32e9c925ba..9ca4b1e392 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -995,6 +995,7 @@ def process_epoch(state: BeaconState) -> None: process_rewards_and_penalties(state) process_registry_updates(state) process_reveal_deadlines(state) + process_challenge_deadlines(state) process_slashings(state) process_final_updates(state) process_custody_final_updates(state) From de03ebb143136caa1a01e2ba8b465aa2c6e224b1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 May 2020 08:31:16 -0600 Subject: [PATCH 027/236] many custody game formatting cleanups --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/custody-game.md | 67 ++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 9ca4b1e392..d53113038f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -63,7 +63,7 @@ - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) - [`process_attestation`](#process_attestation) - - [New deposits](#new-deposits) + - [New default validator for deposits](#new-default-validator-for-deposits) - [New Attester slashing processing](#new-attester-slashing-processing) - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 08893ef532..f4f45ce1e7 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -71,7 +71,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | | `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | -| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 16,384) | epochs | ~9 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days | | `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | ### Max operations per block @@ -311,12 +311,13 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None: # Verify the attestation assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation)) - # Verify it is not too late to challenge - assert (challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY - >= get_current_epoch(state)) + # Verify it is not too late to challenge the attestation + max_attestation_challenge_epoch = challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY + assert get_current_epoch(state) <= max_attestation_challenge_epoch + # Verify it is not too late to challenge the responder responder = state.validators[challenge.responder_index] - assert (responder.exit_epoch == FAR_FUTURE_EPOCH - or responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY >= get_current_epoch(state)) + if responder.exit_epoch < FAR_FUTURE_EPOCH: + assert get_current_epoch(state) <= responder.exit_epoch + MAX_CHUNK_CHALLENGE_DELAY # Verify responder is slashable assert is_slashable_validator(responder, get_current_epoch(state)) # Verify the responder participated in the attestation @@ -332,8 +333,8 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge record.chunk_index != challenge.chunk_index ) # Verify depth - transition_chunks = (challenge.shard_transition.shard_block_lengths[challenge.data_index] - + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK + shard_block_length = challenge.shard_transition.shard_block_lengths[challenge.data_index] + transition_chunks = (shard_block_length + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK assert challenge.chunk_index < transition_chunks # Add new chunk challenge record new_record = CustodyChunkChallengeRecord( @@ -357,10 +358,13 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge def process_chunk_challenge_response(state: BeaconState, response: CustodyChunkResponse) -> None: - challenge = next((record for record in state.custody_chunk_challenge_records if - record.challenge_index == response.challenge_index), None) - assert(challenge is not None) - + # Get matching challenge (if any) from records + matching_challenges = [ + record for record in state.custody_chunk_challenge_records + if record.challenge_index == response.challenge_index + ] + assert len(matching_challenges) > 0 + challenge = matching_challenges[0] # Verify chunk index assert response.chunk_index == challenge.chunk_index # Verify the chunk matches the crosslink data root @@ -391,12 +395,14 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index) custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state)) - # Only past custody periods can be revealed, except after exiting the exit - # period can be revealed - assert (revealer.next_custody_secret_to_reveal < custody_reveal_period - or (revealer.exit_epoch <= get_current_epoch(state) and - revealer.next_custody_secret_to_reveal - <= get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1))) + # Only past custody periods can be revealed, except after exiting the exit period can be revealed + is_past_reveal = revealer.next_custody_secret_to_reveal < custody_reveal_period + is_exited = revealer.exit_epoch <= get_current_epoch(state) + is_exit_period_reveal = ( + revealer.next_custody_secret_to_reveal + == get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1) + ) + assert is_past_reveal or (is_exited and is_exit_period_reveal) # Revealed validator is active or exited, but not withdrawn assert is_slashable_validator(revealer, get_current_epoch(state)) @@ -407,9 +413,7 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal) # Process reveal - if (revealer.exit_epoch <= get_current_epoch(state) and - revealer.next_custody_secret_to_reveal - == get_custody_period_for_validator(reveal.revealer_index, revealer.exit_epoch - 1)): + if is_exited and is_exit_period_reveal: revealer.all_custody_secrets_revealed_epoch = get_current_epoch(state) revealer.next_custody_secret_to_reveal += 1 @@ -556,23 +560,16 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed ### Handling of reveal deadlines -Run `process_reveal_deadlines(state)` after `process_registry_updates(state)`: - ```python def process_reveal_deadlines(state: BeaconState) -> None: epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - if get_custody_period_for_validator(ValidatorIndex(index), epoch) > validator.next_custody_secret_to_reveal \ - + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD): + deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD) + if get_custody_period_for_validator(ValidatorIndex(index), epoch) > deadline: slash_validator(state, ValidatorIndex(index)) ``` -Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`: - ```python -# begin insert @process_challenge_deadlines - process_challenge_deadlines(state) -# end insert @process_challenge_deadlines 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: @@ -583,8 +580,6 @@ def process_challenge_deadlines(state: BeaconState) -> None: ### Final updates -After `process_final_updates(state)`, additional updates are made for the custody game: - ```python def process_custody_final_updates(state: BeaconState) -> None: # Clean up exposed RANDAO key reveals @@ -592,13 +587,11 @@ def process_custody_final_updates(state: BeaconState) -> None: # Reset withdrawable epochs if challenge records are empty records = state.custody_chunk_challenge_records - validator_indices_in_records = set( - [record.responder_index for record in records] - ) + validator_indices_in_records = set([record.responder_index for record in records]) for index, validator in enumerate(state.validators): if validator.exit_epoch != FAR_FUTURE_EPOCH: - if (index in validator_indices_in_records - or validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH): + all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH + if index in validator_indices_in_records or all_secrets_are_revealed: # Delay withdrawable epochs if challenge records are not empty or not all # custody secrets revealed validator.withdrawable_epoch = FAR_FUTURE_EPOCH From 2bf020d49d4f3f7cbc052b2a88a98c2d15ce12e9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 23 Apr 2020 23:46:27 +0800 Subject: [PATCH 028/236] Apply strict uint64 casting --- specs/phase0/beacon-chain.md | 112 ++++++++++++++++++----------------- specs/phase0/fork-choice.md | 8 +-- specs/phase0/validator.md | 2 +- 3 files changed, 63 insertions(+), 59 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 142cf3b025..d1f97a506e 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -170,9 +170,9 @@ The following values are (non-configurable) constants used throughout the specif | `GENESIS_SLOT` | `Slot(0)` | | `GENESIS_EPOCH` | `Epoch(0)` | | `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` | -| `BASE_REWARDS_PER_EPOCH` | `4` | -| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | -| `JUSTIFICATION_BITS_LENGTH` | `4` | +| `BASE_REWARDS_PER_EPOCH` | `uint64(4)` | +| `DEPOSIT_CONTRACT_TREE_DEPTH` | `uint64(2**5)` (= 32) | +| `JUSTIFICATION_BITS_LENGTH` | `uint64(4)` | | `ENDIANNESS` | `'little'` | ## Configuration @@ -183,17 +183,17 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | | - | - | -| `MAX_COMMITTEES_PER_SLOT` | `2**6` (= 64) | -| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) | -| `MAX_VALIDATORS_PER_COMMITTEE` | `2**11` (= 2,048) | -| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | -| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | -| `SHUFFLE_ROUND_COUNT` | `90` | -| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**14` (= 16,384) | -| `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) | -| `HYSTERESIS_QUOTIENT` | `4` | -| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `1` | -| `HYSTERESIS_UPWARD_MULTIPLIER` | `5` | +| `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) | +| `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | +| `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) | +| `MIN_PER_EPOCH_CHURN_LIMIT` | `uint64(2**2)` (= 4) | +| `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) | +| `SHUFFLE_ROUND_COUNT` | `uint64(90)` | +| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) | +| `MIN_GENESIS_TIME` | `uint64(1578009600)` (Jan 3, 2020) | +| `HYSTERESIS_QUOTIENT` | `uint64(4)` | +| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` | +| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` | - For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) @@ -217,37 +217,37 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `MIN_GENESIS_DELAY` | `86400` | seconds | 1 day | -| `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds | -| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds | -| `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 | -| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `2**5` (= 32) | epochs | ~3.4 hours | -| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours | -| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | +<<<<<<< HEAD +| `MIN_GENESIS_DELAY` | `uint64(86400)` | seconds | 1 day | +| `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | +| `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | +| `SLOTS_PER_EPOCH` | `uint64(2**5)` (= 32) | slots | 6.4 minutes | +| `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes | +| `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | +| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | +| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**5)` (= 32) | epochs | ~3.4 hours | +| `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | +| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | | `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | - ### State list lengths | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `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 | ~52,262 years | -| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validators | +| `EPOCHS_PER_HISTORICAL_VECTOR` | `uint64(2**16)` (= 65,536) | epochs | ~0.8 years | +| `EPOCHS_PER_SLASHINGS_VECTOR` | `uint64(2**13)` (= 8,192) | epochs | ~36 days | +| `HISTORICAL_ROOTS_LIMIT` | `uint64(2**24)` (= 16,777,216) | historical roots | ~52,262 years | +| `VALIDATOR_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | validators | ### Rewards and penalties | Name | Value | | - | - | -| `BASE_REWARD_FACTOR` | `2**6` (= 64) | -| `WHISTLEBLOWER_REWARD_QUOTIENT` | `2**9` (= 512) | -| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) | -| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) | -| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) | +| `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) | +| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) | +| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) | +| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**24)` (= 16,777,216) | +| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**5)` (= 32) | - The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12` epochs (about 18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators 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)`. @@ -560,9 +560,9 @@ def integer_squareroot(n: uint64) -> uint64: x = n y = (x + 1) // 2 while y < x: - x = y + x = uint64(y) y = (x + n // x) // 2 - return x + return uint64(x) ``` #### `xor` @@ -592,7 +592,7 @@ def bytes_to_int(data: bytes) -> uint64: """ Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. """ - return int.from_bytes(data, ENDIANNESS) + return uint64(int.from_bytes(data, ENDIANNESS)) ``` ### Crypto @@ -731,14 +731,18 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) # See the 'generalized domain' algorithm on page 3 - for current_round in range(SHUFFLE_ROUND_COUNT): - pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=1))[0:8]) % index_count + for current_round in map(uint64, range(SHUFFLE_ROUND_COUNT)): + pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=uint64(1)))[0:8]) % index_count flip = (pivot + index_count - index) % index_count position = max(index, flip) - source = hash(seed + int_to_bytes(current_round, length=1) + int_to_bytes(position // 256, length=4)) + source = hash( + seed + + int_to_bytes(current_round, length=uint64(1)) + + int_to_bytes(uint64(position // 256), length=uint64(4)) + ) byte = source[(position % 256) // 8] bit = (byte >> (position % 8)) % 2 - index = flip if bit else index + index = uint64(flip) if bit else index return index ``` @@ -754,8 +758,8 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] MAX_RANDOM_BYTE = 2**8 - 1 i = 0 while True: - candidate_index = indices[compute_shuffled_index(i % len(indices), len(indices), seed)] - random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32] + candidate_index = indices[compute_shuffled_index(uint64(i % len(indices)), uint64(len(indices)), seed)] + random_byte = hash(seed + int_to_bytes(uint64(i // 32), length=uint64(8)))[i % 32] effective_balance = state.validators[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index @@ -774,7 +778,7 @@ def compute_committee(indices: Sequence[ValidatorIndex], """ start = (len(indices) * index) // count end = (len(indices) * (index + 1)) // count - return [indices[compute_shuffled_index(i, len(indices), seed)] for i in range(start, end)] + return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] ``` #### `compute_epoch_at_slot` @@ -933,7 +937,7 @@ def get_validator_churn_limit(state: BeaconState) -> uint64: Return the validator churn limit for the current epoch. """ active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(MIN_PER_EPOCH_CHURN_LIMIT, len(active_validator_indices) // CHURN_LIMIT_QUOTIENT) + return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices) // CHURN_LIMIT_QUOTIENT)) ``` #### `get_seed` @@ -944,7 +948,7 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes Return the seed at ``epoch``. """ mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + int_to_bytes(epoch, length=8) + mix) + return hash(domain_type + int_to_bytes(epoch, length=uint64(8)) + mix) ``` #### `get_committee_count_at_slot` @@ -955,9 +959,9 @@ def get_committee_count_at_slot(state: BeaconState, slot: Slot) -> uint64: Return the number of committees at ``slot``. """ epoch = compute_epoch_at_slot(slot) - return max(1, min( + return max(uint64(1), min( MAX_COMMITTEES_PER_SLOT, - len(get_active_validator_indices(state, epoch)) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + uint64(len(get_active_validator_indices(state, epoch)) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE), )) ``` @@ -973,8 +977,8 @@ def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) return compute_committee( indices=get_active_validator_indices(state, epoch), seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), - index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, - count=committees_per_slot * SLOTS_PER_EPOCH, + index=uint64((slot % SLOTS_PER_EPOCH) * committees_per_slot + index), + count=uint64(committees_per_slot * SLOTS_PER_EPOCH), ) ``` @@ -986,7 +990,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: Return the beacon proposer index at the current slot. """ epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + int_to_bytes(state.slot, length=8)) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + int_to_bytes(state.slot, length=uint64(8))) indices = get_active_validator_indices(state, epoch) return compute_proposer_index(state, indices, seed) ``` @@ -1094,7 +1098,7 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: exit_queue_epoch = max(exit_epochs + [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) + exit_queue_epoch = Epoch(exit_queue_epoch + 1) # Set validator exit epoch and withdrawable epoch validator.exit_epoch = exit_queue_epoch @@ -1728,13 +1732,13 @@ 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=uint64(DEPOSIT_CONTRACT_TREE_DEPTH + 1), # Add 1 for the List length mix-in index=state.eth1_deposit_index, root=state.eth1_data.deposit_root, ) # Deposits must be processed in order - state.eth1_deposit_index += 1 + state.eth1_deposit_index = uint64(state.eth1_deposit_index + 1) pubkey = deposit.data.pubkey amount = deposit.data.amount diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 4ed2733e1a..fb5740ae55 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -100,7 +100,7 @@ _The block for `anchor_root` is incorrectly initialized to the block header, rat ```python def get_forkchoice_store(anchor_state: BeaconState) -> Store: - anchor_block_header = anchor_state.latest_block_header.copy() + anchor_block_header: BeaconBlockHeader = anchor_state.latest_block_header.copy() if anchor_block_header.state_root == Bytes32(): anchor_block_header.state_root = hash_tree_root(anchor_state) anchor_root = hash_tree_root(anchor_block_header) @@ -108,7 +108,7 @@ def get_forkchoice_store(anchor_state: BeaconState) -> Store: justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) return Store( - time=anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot, + time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, @@ -300,7 +300,7 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None: def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: # Store target checkpoint state if not yet seen if target not in store.checkpoint_states: - base_state = store.block_states[target.root].copy() + base_state: BeaconState = store.block_states[target.root].copy() process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) store.checkpoint_states[target] = base_state ``` @@ -344,7 +344,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: block = signed_block.message # Make a copy of the state to avoid mutability issues assert block.parent_root in store.block_states - pre_state = store.block_states[block.parent_root].copy() + pre_state: BeaconState = store.block_states[block.parent_root].copy() # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. assert get_current_slot(store) >= block.slot # Add new block to the store diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index adf23c8408..630d429507 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -270,7 +270,7 @@ An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state)` wher ```python def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - return state.genesis_time + slot * SECONDS_PER_SLOT + return uint64(state.genesis_time + slot * SECONDS_PER_SLOT) ``` ```python From 88d7315739b475f3297151f27866f8bef39cc7f5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 May 2020 21:49:17 +0800 Subject: [PATCH 029/236] Fix typing --- specs/phase0/beacon-chain.md | 4 ++-- specs/phase0/validator.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index d1f97a506e..2678a379fd 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1476,12 +1476,12 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ - source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] + Gwei(source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i]) for i in range(len(state.validators)) ] penalties = [ - source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] + Gwei(source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i]) for i in range(len(state.validators)) ] diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 630d429507..805498250c 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -430,7 +430,7 @@ def compute_subnet_for_attestation(state: BeaconState, attestation: Attestation) slots_since_epoch_start = attestation.data.slot % SLOTS_PER_EPOCH committees_since_epoch_start = get_committee_count_at_slot(state, attestation.data.slot) * slots_since_epoch_start - return (committees_since_epoch_start + attestation.data.index) % ATTESTATION_SUBNET_COUNT + return uint64((committees_since_epoch_start + attestation.data.index) % ATTESTATION_SUBNET_COUNT) ``` ### Attestation aggregation From 9f340d5fd1b0b2e0b7c014e24a9c0787b433baea Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 May 2020 21:53:14 +0800 Subject: [PATCH 030/236] (i) Fix leftover (ii) `SHARD_COMMITTEE_PERIOD` should be uint64 --- specs/phase0/beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 2678a379fd..60ed518f63 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -217,7 +217,6 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | Duration | | - | - | :-: | :-: | -<<<<<<< HEAD | `MIN_GENESIS_DELAY` | `uint64(86400)` | seconds | 1 day | | `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | @@ -228,7 +227,7 @@ The following values are (non-configurable) constants used throughout the specif | `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**5)` (= 32) | epochs | ~3.4 hours | | `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | -| `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | +| `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | ### State list lengths From 8f70453aeff73b37304abc77983af6b4d4dac1e1 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 May 2020 23:09:07 +0800 Subject: [PATCH 031/236] PR feedback from Proto --- specs/phase0/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 60ed518f63..4f2f98458f 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -732,7 +732,7 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> # See the 'generalized domain' algorithm on page 3 for current_round in map(uint64, range(SHUFFLE_ROUND_COUNT)): pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=uint64(1)))[0:8]) % index_count - flip = (pivot + index_count - index) % index_count + flip = uint64((pivot + index_count - index) % index_count) position = max(index, flip) source = hash( seed @@ -741,7 +741,7 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> ) byte = source[(position % 256) // 8] bit = (byte >> (position % 8)) % 2 - index = uint64(flip) if bit else index + index = flip if bit else index return index ``` @@ -1097,7 +1097,7 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: exit_queue_epoch = max(exit_epochs + [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(exit_queue_epoch + 1) + exit_queue_epoch += Epoch(1) # Set validator exit epoch and withdrawable epoch validator.exit_epoch = exit_queue_epoch From cd91380d8056cf86ef98690a5ee7192db5a4f824 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 18 May 2020 14:34:12 +0800 Subject: [PATCH 032/236] PR feedback from proto: set `length` back to `int` --- specs/phase0/beacon-chain.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 4f2f98458f..8e212ba13c 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -577,7 +577,7 @@ def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: #### `int_to_bytes` ```python -def int_to_bytes(n: uint64, length: uint64) -> bytes: +def int_to_bytes(n: uint64, length: int) -> bytes: """ Return the ``length``-byte serialization of ``n`` in ``ENDIANNESS``-endian. """ @@ -731,13 +731,13 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) # See the 'generalized domain' algorithm on page 3 for current_round in map(uint64, range(SHUFFLE_ROUND_COUNT)): - pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=uint64(1)))[0:8]) % index_count + pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=1))[0:8]) % index_count flip = uint64((pivot + index_count - index) % index_count) position = max(index, flip) source = hash( seed - + int_to_bytes(current_round, length=uint64(1)) - + int_to_bytes(uint64(position // 256), length=uint64(4)) + + int_to_bytes(current_round, length=1) + + int_to_bytes(uint64(position // 256), length=4) ) byte = source[(position % 256) // 8] bit = (byte >> (position % 8)) % 2 @@ -758,7 +758,7 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] i = 0 while True: candidate_index = indices[compute_shuffled_index(uint64(i % len(indices)), uint64(len(indices)), seed)] - random_byte = hash(seed + int_to_bytes(uint64(i // 32), length=uint64(8)))[i % 32] + random_byte = hash(seed + int_to_bytes(uint64(i // 32), length=8))[i % 32] effective_balance = state.validators[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index @@ -947,7 +947,7 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes Return the seed at ``epoch``. """ mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + int_to_bytes(epoch, length=uint64(8)) + mix) + return hash(domain_type + int_to_bytes(epoch, length=8) + mix) ``` #### `get_committee_count_at_slot` @@ -989,7 +989,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: Return the beacon proposer index at the current slot. """ epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + int_to_bytes(state.slot, length=uint64(8))) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + int_to_bytes(state.slot, length=8)) indices = get_active_validator_indices(state, epoch) return compute_proposer_index(state, indices, seed) ``` From 2dc041807af913f16e54cf44a46fef07f80dbe2e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 23:50:18 +0800 Subject: [PATCH 033/236] Implement `get_start_shard` --- setup.py | 15 ++++- specs/phase1/beacon-chain.md | 61 ++++++++++++++++++- specs/phase1/phase1-fork.md | 1 + .../test_process_crosslink.py | 2 +- .../test/phase_1/sanity/test_blocks.py | 4 +- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 4f3f2b8724..d0f063b335 100644 --- a/setup.py +++ b/setup.py @@ -140,7 +140,7 @@ def get_spec(file_name: str) -> SpecObject: def ceillog2(x: uint64) -> int: return (x - 1).bit_length() ''' -SUNDRY_FUNCTIONS = ''' +PHASE0_SUNDRY_FUNCTIONS = ''' # Monkey patch hash cache _hash = hash hash_cache: Dict[bytes, Bytes32] = {} @@ -220,6 +220,13 @@ def wrapper(*args, **kw): # type: ignore _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)''' +PHASE1_SUNDRY_FUNCTIONS = ''' +_get_start_shard = get_start_shard +get_start_shard = cache_this( + lambda state, slot: (state.validators.hash_tree_root(), slot), + _get_start_shard, lru_size=SLOTS_PER_EPOCH * 3)''' + + def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str: """ Given all the objects that constitute a spec, combine them into a single pyfile. @@ -250,9 +257,11 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str: + '\n\n' + CONFIG_LOADER + '\n\n' + ssz_objects_instantiation_spec + '\n\n' + functions_spec - + '\n' + SUNDRY_FUNCTIONS - + '\n' + + '\n' + PHASE0_SUNDRY_FUNCTIONS ) + if fork == 'phase1': + spec += '\n' + PHASE1_SUNDRY_FUNCTIONS + spec += '\n' return spec diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 39a73c0aa0..124442b595 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -47,6 +47,7 @@ - [`get_light_client_committee`](#get_light_client_committee) - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_indexed_attestation`](#get_indexed_attestation) + - [`get_committee_count_delta`](#get_committee_count_delta) - [`get_start_shard`](#get_start_shard) - [`get_shard`](#get_shard) - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) @@ -69,6 +70,7 @@ - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) - [Epoch transition](#epoch-transition) + - [Phase 1 final updates](#phase-1-final-updates) - [Custody game updates](#custody-game-updates) - [Online-tracking](#online-tracking) - [Light client committee updates](#light-client-committee-updates) @@ -280,6 +282,7 @@ class BeaconState(Container): current_justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint # Phase 1 + current_epoch_start_shard: Shard shard_states: List[ShardState, MAX_SHARDS] online_countdown: List[OnlineEpochs, VALIDATOR_REGISTRY_LIMIT] # not a raw byte array, considered its large size. current_light_committee: CompactCommittee @@ -530,18 +533,53 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) ) ``` +#### `get_committee_count_delta` + +```python +def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: Slot) -> uint64: + """ + Return the sum of committee counts between ``[start_slot, stop_slot)``. + """ + committee_sum = 0 + for slot in range(start_slot, stop_slot): + count = get_committee_count_at_slot(state, Slot(slot)) + committee_sum += count + return committee_sum +``` + #### `get_start_shard` ```python def get_start_shard(state: BeaconState, slot: Slot) -> Shard: - # TODO: implement start shard logic - return Shard(0) + """ + Return the start shard at ``slot``. + """ + current_epoch_start_slot = compute_start_slot_at_epoch(get_current_epoch(state)) + active_shard_count = get_active_shard_count(state) + if current_epoch_start_slot == slot: + return state.current_epoch_start_shard + elif current_epoch_start_slot > slot: + # Current epoch or the next epoch lookahead + shard_delta = get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) + return Shard((state.current_epoch_start_shard + shard_delta) % active_shard_count) + else: + # Previous epoch + shard_delta = get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot) + max_committees_per_epoch = MAX_COMMITTEES_PER_SLOT * SLOTS_PER_EPOCH + return Shard( + # Ensure positive + (state.current_epoch_start_shard + max_committees_per_epoch * active_shard_count - shard_delta) + % active_shard_count + ) ``` #### `get_shard` ```python def get_shard(state: BeaconState, attestation: Attestation) -> Shard: + """ + Return the shard that the given attestation is attesting. + """ return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) ``` @@ -549,6 +587,9 @@ def get_shard(state: BeaconState, attestation: Attestation) -> Shard: ```python def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: + """ + Return the latest slot number of the given shard. + """ return state.shard_states[shard].slot ``` @@ -556,7 +597,11 @@ def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: ```python def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: - return compute_offset_slots(state.shard_states[shard].slot, state.slot) + """ + Return the offset slots of the given shard. + The offset slot are after the latest slot and before current slot. + """ + return compute_offset_slots(get_latest_slot_for_shard(state, shard), state.slot) ``` ### Predicates @@ -993,9 +1038,19 @@ def process_epoch(state: BeaconState) -> None: process_reveal_deadlines(state) process_slashings(state) process_final_updates(state) + process_phase_1_final_updates(state) +``` + +#### Phase 1 final updates + +```python +def process_phase_1_final_updates(state: BeaconState) -> None: process_custody_final_updates(state) process_online_tracking(state) process_light_client_committee_updates(state) + + # Update current_epoch_start_shard + state.current_epoch_start_shard = get_start_shard(state, state.slot) ``` #### Custody game updates diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index cc7d8f33ee..d362ed6334 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -99,6 +99,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, # Phase 1 + current_epoch_start_shard=Shard(0), shard_states=List[ShardState, MAX_SHARDS]( ShardState( slot=pre.slot, diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py index 1f066b3442..af0ff1a906 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -18,7 +18,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` slot_x = state.slot committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) assert state.shard_states[shard].slot == slot_x - 1 # Create SignedShardBlock diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 60af35d45d..1499d34cdd 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -67,7 +67,7 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) assert state.shard_states[shard].slot == state.slot - 1 pre_gasprice = state.shard_states[shard].gasprice @@ -93,7 +93,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) assert state.shard_states[shard].slot == state.slot - 1 # No new shard block From 870ad8b921705cb658ebb0b88ff4e08bdeb54609 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 30 May 2020 03:56:56 +0800 Subject: [PATCH 034/236] Fix test --- .../pyspec/eth2spec/test/fork_choice/test_on_shard_head.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index f4b883f06c..5b4205e91d 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -30,6 +30,7 @@ def run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) # Create SignedShardBlock body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + target_len_offset_slot = 1 shard_block = build_shard_block(spec, state, shard, body=body, signed=True) shard_blocks = [shard_block] @@ -38,17 +39,15 @@ def run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) shard_transitions = build_shard_transitions_till_slot( spec, state, - shards=[shard, ], shard_blocks={shard: shard_blocks}, - target_len_offset_slot=1, + on_time_slot=state.slot + target_len_offset_slot, ) shard_transition = shard_transitions[shard] attestation = build_attestation_with_shard_transition( spec, state, - slot=state.slot, index=committee_index, - target_len_offset_slot=1, + on_time_slot=state.slot + target_len_offset_slot, shard_transition=shard_transition, ) From 92db6da50854071eecea138576e9771a5004a596 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 17:56:22 +0800 Subject: [PATCH 035/236] Apply suggestions from Terence Co-authored-by: terence tsao --- specs/phase1/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 124442b595..1367d029b9 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -578,7 +578,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard: ```python def get_shard(state: BeaconState, attestation: Attestation) -> Shard: """ - Return the shard that the given attestation is attesting. + Return the shard that the given ``attestation`` is attesting. """ return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) ``` @@ -588,7 +588,7 @@ def get_shard(state: BeaconState, attestation: Attestation) -> Shard: ```python def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: """ - Return the latest slot number of the given shard. + Return the latest slot number of the given ``shard``. """ return state.shard_states[shard].slot ``` @@ -598,7 +598,7 @@ def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: ```python def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: """ - Return the offset slots of the given shard. + Return the offset slots of the given ``shard``. The offset slot are after the latest slot and before current slot. """ return compute_offset_slots(get_latest_slot_for_shard(state, shard), state.slot) From 30f72dd69646d037a06943d026e2d91ef2b9c2c5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 23:15:16 +0800 Subject: [PATCH 036/236] Fix `get_shard` and `compute_shard_from_committee_index` calls --- specs/phase1/beacon-chain.md | 25 ++++++------- .../eth2spec/test/helpers/attestations.py | 37 +++++++++---------- .../eth2spec/test/helpers/shard_block.py | 2 +- .../test/helpers/shard_transitions.py | 15 ++++++++ .../test/phase_0/sanity/test_blocks.py | 20 +++++++--- .../test_process_shard_transition.py | 9 ++--- .../test/phase_1/sanity/test_blocks.py | 4 +- 7 files changed, 67 insertions(+), 45 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 228cd888e3..52b6f2afb5 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -578,7 +578,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard: active_shard_count = get_active_shard_count(state) if current_epoch_start_slot == slot: return state.current_epoch_start_shard - elif current_epoch_start_slot > slot: + elif slot > current_epoch_start_slot: # Current epoch or the next epoch lookahead shard_delta = get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) return Shard((state.current_epoch_start_shard + shard_delta) % active_shard_count) @@ -693,8 +693,7 @@ def is_on_time_attestation(state: BeaconState, """ Check if the given attestation is on-time. """ - # TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1 - return attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot + return attestation.data.slot == compute_previous_slot(state.slot) ``` #### `is_winning_attestation` @@ -803,20 +802,19 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint - shard = get_shard(state, attestation) - # Type 1: on-time attestations, the custody bits should be non-empty. if attestation.custody_bits_blocks != []: # Ensure on-time attestation assert is_on_time_attestation(state, attestation) # Correct data root count + shard = get_shard(state, attestation) assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) # Type 2: no shard transition, no custody bits else: # Ensure delayed attestation - assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY < state.slot + assert data.slot < compute_previous_slot(state.slot) # Late attestations cannot have a shard transition root assert data.shard_transition_root == Root() @@ -911,9 +909,10 @@ def process_crosslink_for_shard(state: BeaconState, committee_index: CommitteeIndex, shard_transition: ShardTransition, attestations: Sequence[Attestation]) -> Root: - committee = get_beacon_committee(state, state.slot, committee_index) + on_time_attestation_slot = compute_previous_slot(state.slot) + committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) online_indices = get_online_validator_indices(state) - shard = compute_shard_from_committee_index(state, committee_index, state.slot) + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) # Loop over all shard transition roots shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) @@ -968,15 +967,15 @@ def process_crosslink_for_shard(state: BeaconState, def process_crosslinks(state: BeaconState, shard_transitions: Sequence[ShardTransition], attestations: Sequence[Attestation]) -> None: - committee_count = get_committee_count_at_slot(state, state.slot) + on_time_attestation_slot = compute_previous_slot(state.slot) + committee_count = get_committee_count_at_slot(state, on_time_attestation_slot) for committee_index in map(CommitteeIndex, range(committee_count)): - shard = compute_shard_from_committee_index(state, committee_index, state.slot) # All attestations in the block for this committee/shard and current slot shard_attestations = [ attestation for attestation in attestations if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] - + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) winning_root = process_crosslink_for_shard(state, committee_index, shard_transitions[shard], shard_attestations) if winning_root != Root(): # Mark relevant pending attestations as creating a successful crosslink @@ -1083,7 +1082,7 @@ def process_epoch(state: BeaconState) -> None: process_registry_updates(state) process_reveal_deadlines(state) process_slashings(state) - process_final_updates(state) + process_final_updates(state) # phase 0 final updates process_phase_1_final_updates(state) ``` @@ -1096,7 +1095,7 @@ def process_phase_1_final_updates(state: BeaconState) -> None: process_light_client_committee_updates(state) # Update current_epoch_start_shard - state.current_epoch_start_shard = get_start_shard(state, state.slot) + state.current_epoch_start_shard = get_start_shard(state, Slot(state.slot + 1)) ``` #### Custody game updates diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index c533182eff..1372b0654e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,8 +1,9 @@ from typing import List from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 -from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot, transition_to +from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist @@ -81,12 +82,12 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: - # No shard transition + # No shard transition -> no shard block shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) if on_time: temp_state = state.copy() next_slot(spec, temp_state) - shard_transition = spec.get_shard_transition(temp_state, shard, []) + shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=[]) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() @@ -180,7 +181,7 @@ def get_valid_attestation(spec, fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set) if spec.fork == PHASE1 and on_time: - attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed=signed) return attestation @@ -317,7 +318,19 @@ def next_epoch_with_attestations(spec, committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): for index in range(committees_per_slot): - cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True) + if spec.fork == PHASE1: + shard = spec.compute_shard_from_committee_index(post_state, index, slot_to_attest) + shard_transition = get_shard_transition_of_committee( + spec, post_state, index, slot=slot_to_attest + ) + block.body.shard_transitions[shard] = shard_transition + else: + shard_transition = None + + cur_attestation = get_valid_attestation( + spec, post_state, slot_to_attest, + shard_transition=shard_transition, index=index, signed=True, on_time=True + ) block.body.attestations.append(cur_attestation) if fill_prev_epoch: @@ -328,9 +341,6 @@ def next_epoch_with_attestations(spec, spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) block.body.attestations.append(prev_attestation) - if spec.fork == PHASE1: - fill_block_shard_transitions_by_attestations(spec, post_state, block) - signed_block = state_transition_and_sign_block(spec, post_state, block) signed_blocks.append(signed_block) @@ -396,14 +406,3 @@ def cached_prepare_state_with_attestations(spec, state): # Put the LRU cache result into the state view, as if we transitioned the original view state.set_backing(_prep_state_cache_dict[key]) - - -def fill_block_shard_transitions_by_attestations(spec, state, block): - block.body.shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS - for attestation in block.body.attestations: - shard = spec.get_shard(state, attestation) - if attestation.data.slot == state.slot: - temp_state = state.copy() - transition_to(spec, temp_state, slot=block.slot) - shard_transition = spec.get_shard_transition(temp_state, shard, []) - block.body.shard_transitions[shard] = shard_transition diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 805b955f79..58efada835 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -70,7 +70,7 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): return shard_transitions -def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition=None): +def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition): temp_state = state.copy() transition_to(spec, temp_state, on_time_slot - 1) attestation = get_valid_on_time_attestation( diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index 4ac0ddcfb5..abb5e7278e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -1,4 +1,5 @@ from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.state import transition_to def run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=True): @@ -26,3 +27,17 @@ def run_shard_transitions_processing(spec, state, shard_transitions, attestation # yield post-state yield 'post', state + + +def get_shard_transition_of_committee(spec, state, committee_index, slot=None, shard_blocks=None): + if shard_blocks is None: + shard_blocks = [] + + if slot is None: + slot = state.slot + + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + temp_state = state.copy() + transition_to(spec, temp_state, slot + 1) + shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=shard_blocks) + return shard_transition diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py index f0cfc462e6..1e057e5a95 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py @@ -16,8 +16,9 @@ get_indexed_attestation_participants, ) from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect -from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations +from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.context import ( spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases, @@ -687,14 +688,23 @@ def test_attestation(spec, state): yield 'pre', state - attestation = get_valid_attestation(spec, state, signed=True, on_time=True) + attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + index = 0 + if spec.fork == PHASE1: + shard = spec.compute_shard_from_committee_index(state, index, state.slot) + shard_transition = get_shard_transition_of_committee(spec, state, index) + attestation_block.body.shard_transitions[shard] = shard_transition + else: + shard_transition = None + + attestation = get_valid_attestation( + spec, state, shard_transition=shard_transition, index=index, signed=True, on_time=True + ) # Add to state via block transition pre_current_attestations_len = len(state.current_epoch_attestations) - attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) attestation_block.body.attestations.append(attestation) - if spec.fork == PHASE1: - fill_block_shard_transitions_by_attestations(spec, state, attestation_block) signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block) assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1 diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py index ba408cd484..00ffbe0a80 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py @@ -15,11 +15,10 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): state = transition_to_valid_shard_slot(spec, state) - # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` - slot_x = state.slot + init_slot = state.slot committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) - assert state.shard_states[shard].slot == slot_x - 1 + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) + assert state.shard_states[shard].slot == state.slot - 1 # Create SignedShardBlock body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE @@ -50,7 +49,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): if valid: # After state transition, - assert state.slot == slot_x + target_len_offset_slot + assert state.slot == init_slot + target_len_offset_slot shard_state = state.shard_states[shard] assert shard_state != pre_shard_state assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 1499d34cdd..0175bd40da 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -67,7 +67,7 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 pre_gasprice = state.shard_states[shard].gasprice @@ -93,7 +93,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 # No new shard block From 5f10ac13bf91c181152a5469562dfb9d89383926 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 23:22:59 +0800 Subject: [PATCH 037/236] PR feedback from Terence and Danny: refactor `get_committee_count_delta` --- specs/phase1/beacon-chain.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 52b6f2afb5..484b24cf17 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -558,13 +558,9 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) ```python def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: Slot) -> uint64: """ - Return the sum of committee counts between ``[start_slot, stop_slot)``. + Return the sum of committee counts in range ``[start_slot, stop_slot)``. """ - committee_sum = 0 - for slot in range(start_slot, stop_slot): - count = get_committee_count_at_slot(state, Slot(slot)) - committee_sum += count - return committee_sum + return sum(get_committee_count_at_slot(state, Slot(slot)) for slot in range(start_slot, stop_slot)) ``` #### `get_start_shard` From 142ba17451e05b0e6478796fe9bea9eb2de4c488 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 2 Jun 2020 18:08:28 +0800 Subject: [PATCH 038/236] PR review from Danny --- specs/phase1/shard-fork-choice.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index b026aabfc9..867730203f 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -59,7 +59,9 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro return Gwei(sum( state.validators[i].effective_balance for i in active_indices if ( - i in store.latest_messages and get_shard_ancestor( + i in store.latest_messages and + store.latest_messages[i].shard == shard_store.shard and + get_shard_ancestor( store, shard_store, store.latest_messages[i].root, shard_store.blocks[root].slot ) == root ) @@ -71,12 +73,18 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro ```python def get_shard_head(store: Store, shard_store: ShardStore) -> Root: # Execute the LMD-GHOST fork choice + shard_blocks = shard_store.blocks head_beacon_root = get_head(store) - head_shard_root = store.block_states[head_beacon_root].shard_states[shard_store.shard].latest_block_root + head_shard_state = store.block_states[head_beacon_root].shard_states[shard_store.shard] + head_shard_root = head_shard_state.latest_block_root while True: + # Find the valid child block roots children = [ root for root in shard_store.blocks.keys() - if shard_store.blocks[root].shard_parent_root == head_shard_root + if ( + shard_blocks[root].shard_parent_root == head_shard_root + and shard_blocks[root].slot > head_shard_state.slot + ) ] if len(children) == 0: return head_shard_root @@ -116,14 +124,15 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si assert shard_block.beacon_parent_root in store.block_states beacon_state = store.block_states[shard_block.beacon_parent_root] - # 3. Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - assert shard_block.slot > finalized_slot + # 3. Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor) + finalized_beacon_state = store.block_states[store.finalized_checkpoint.root] + finalized_shard_state = finalized_beacon_state.shard_states[shard] + assert shard_block.slot > finalized_shard_state.slot # 4. Check block is a descendant of the finalized block at the checkpoint finalized slot + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) assert ( - shard_block.beacon_parent_root == store.finalized_checkpoint.root - or get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root + get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root ) # Add new block to the store From 5c5cedd60d1c08583d8627e93db1b1b179f6b7f4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 3 Jun 2020 22:31:16 +0800 Subject: [PATCH 039/236] Apply PR feedback from Danny and Terence --- specs/phase1/shard-fork-choice.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 867730203f..3b6bc5ac9b 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -6,7 +6,7 @@ -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + - [Introduction](#introduction) - [Fork choice](#fork-choice) @@ -23,7 +23,7 @@ ## Introduction -This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase 1. +This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase 1. It assumes the [beacon chain fork choice spec](./fork-choice.md). ## Fork choice @@ -59,9 +59,9 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro return Gwei(sum( state.validators[i].effective_balance for i in active_indices if ( - i in store.latest_messages and - store.latest_messages[i].shard == shard_store.shard and - get_shard_ancestor( + i in store.latest_messages + and store.latest_messages[i].shard == shard_store.shard + and get_shard_ancestor( store, shard_store, store.latest_messages[i].root, shard_store.blocks[root].slot ) == root ) @@ -116,20 +116,25 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: SignedShardBlock) -> None: shard_block = signed_shard_block.message shard = shard_store.shard - # 1. Check shard parent exists + + # Check shard + # TODO: check it in networking spec + assert shard_block.shard == shard + + # Check shard parent exists assert shard_block.shard_parent_root in shard_store.block_states pre_shard_state = shard_store.block_states[shard_block.shard_parent_root] - # 2. Check beacon parent exists + # Check beacon parent exists assert shard_block.beacon_parent_root in store.block_states beacon_state = store.block_states[shard_block.beacon_parent_root] - # 3. Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor) + # Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor) finalized_beacon_state = store.block_states[store.finalized_checkpoint.root] finalized_shard_state = finalized_beacon_state.shard_states[shard] assert shard_block.slot > finalized_shard_state.slot - # 4. Check block is a descendant of the finalized block at the checkpoint finalized slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) assert ( get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root From 68e934bf1552517346c1b31f1fb9e181b51d82cb Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 3 Jun 2020 23:08:38 +0800 Subject: [PATCH 040/236] Add `get_start_shard` unittests and update minimal config 1. Add unittests for testing `get_start_shard` with better granularity 2. Change `INITIAL_ACTIVE_SHARDS` from `4` to `2` for tight crosslinking --- configs/minimal.yaml | 2 +- .../test/phase_1/unittests/__init__.py | 0 .../phase_1/unittests/test_get_start_shard.py | 71 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py diff --git a/configs/minimal.yaml b/configs/minimal.yaml index d8e346ffab..bb46294f5b 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -165,7 +165,7 @@ PHASE_1_FORK_VERSION: 0x01000001 # [customized] for testing PHASE_1_GENESIS_SLOT: 8 # [customized] reduced for testing -INITIAL_ACTIVE_SHARDS: 4 +INITIAL_ACTIVE_SHARDS: 2 # Phase 1: General diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py new file mode 100644 index 0000000000..f9f605d9fb --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py @@ -0,0 +1,71 @@ +from eth2spec.test.context import ( + PHASE0, + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.helpers.state import next_epoch + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_committee_count_delta(spec, state): + assert spec.get_committee_count_delta(state, 0, 0) == 0 + assert spec.get_committee_count_at_slot(state, 0) != 0 + assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_at_slot(state, 0) + assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_at_slot(state, 1) + assert spec.get_committee_count_delta(state, 0, 2) == ( + spec.get_committee_count_at_slot(state, 0) + spec.get_committee_count_at_slot(state, 1) + ) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_start_shard_current_epoch_start(spec, state): + assert state.current_epoch_start_shard == 0 + next_epoch(spec, state) + active_shard_count = spec.get_active_shard_count(state) + assert state.current_epoch_start_shard == ( + spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) % active_shard_count + ) + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + + slot = current_epoch_start_slot + start_shard = spec.get_start_shard(state, slot) + assert start_shard == state.current_epoch_start_shard + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_start_shard_next_slot(spec, state): + next_epoch(spec, state) + active_shard_count = spec.get_active_shard_count(state) + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + + slot = current_epoch_start_slot + 1 + start_shard = spec.get_start_shard(state, slot) + + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + expected_start_shard = ( + state.current_epoch_start_shard + + spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) + ) % active_shard_count + assert start_shard == expected_start_shard + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_start_shard_previous_slot(spec, state): + next_epoch(spec, state) + active_shard_count = spec.get_active_shard_count(state) + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + + slot = current_epoch_start_slot - 1 + start_shard = spec.get_start_shard(state, slot) + + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + expected_start_shard = ( + state.current_epoch_start_shard + + spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH * active_shard_count + - spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot) + ) % active_shard_count + assert start_shard == expected_start_shard From a685be3bbecb18d7bf336af911a638d68e6b64f9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 00:42:08 +0800 Subject: [PATCH 041/236] PR feedback from Danny Co-authored-by: Danny Ryan --- .../eth2spec/test/phase_1/unittests/test_get_start_shard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py index f9f605d9fb..27afd4a4e8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py @@ -46,8 +46,8 @@ def test_get_start_shard_next_slot(spec, state): current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) expected_start_shard = ( - state.current_epoch_start_shard + - spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) + state.current_epoch_start_shard + + spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) ) % active_shard_count assert start_shard == expected_start_shard From e1981a7bfdeb2baf296cbfdb1f74169b56341883 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 01:00:52 +0800 Subject: [PATCH 042/236] `head_shard_root` -> `shard_head_root` --- specs/phase1/fork-choice.md | 2 +- specs/phase1/shard-fork-choice.md | 8 ++++---- .../eth2spec/test/fork_choice/test_on_attestation.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md index f4e771ddb4..3f9fbdbfba 100644 --- a/specs/phase1/fork-choice.md +++ b/specs/phase1/fork-choice.md @@ -51,7 +51,7 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn for i in attesting_indices: if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: store.latest_messages[i] = LatestMessage( - epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.head_shard_root + epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root ) ``` diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 3b6bc5ac9b..fb98893ac4 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -76,20 +76,20 @@ def get_shard_head(store: Store, shard_store: ShardStore) -> Root: shard_blocks = shard_store.blocks head_beacon_root = get_head(store) head_shard_state = store.block_states[head_beacon_root].shard_states[shard_store.shard] - head_shard_root = head_shard_state.latest_block_root + shard_head_root = head_shard_state.latest_block_root while True: # Find the valid child block roots children = [ root for root in shard_store.blocks.keys() if ( - shard_blocks[root].shard_parent_root == head_shard_root + shard_blocks[root].shard_parent_root == shard_head_root and shard_blocks[root].slot > head_shard_state.slot ) ] if len(children) == 0: - return head_shard_root + return shard_head_root # Sort by latest attesting balance with ties broken lexicographically - head_shard_root = max( + shard_head_root = max( children, key=lambda root: (get_shard_latest_attesting_balance(store, shard_store, root), root) ) ``` diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index 04d2588d93..a5334c5c78 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -32,7 +32,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True): epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, shard=spec.get_shard(state, attestation), - shard_root=attestation.data.head_shard_root, + shard_root=attestation.data.shard_head_root, ) assert ( From 74204f795d72a4b0322cac5e2ee5a4ea7ebc257a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 3 Jun 2020 12:16:39 -0600 Subject: [PATCH 043/236] udpate validator guide to work with all updated phase 1 constructions --- specs/phase1/beacon-chain.md | 3 + specs/phase1/shard-transition.md | 138 ------------------ specs/phase1/validator.md | 84 +++++++++-- .../test/validator/test_validator_unittest.py | 47 ++++-- 4 files changed, 112 insertions(+), 160 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0af51a815a..1fabe0370c 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -854,6 +854,9 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr shard_parent_root = hash_tree_root(header) headers.append(header) proposers.append(proposal_index) + else: + # Must have a stub for `shard_data_root` if empty slot + assert transition.shard_data_roots[i] == Root() prev_gasprice = shard_state.gasprice diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index e6221a9805..ec764f7b24 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -154,141 +154,3 @@ def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool: # TODO ... ``` - -## Honest committee member behavior - -### Helper functions - -```python -def get_winning_proposal(beacon_state: BeaconState, proposals: Sequence[SignedShardBlock]) -> SignedShardBlock: - # TODO: Let `winning_proposal` be the proposal with the largest number of total attestations from slots in - # `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing - # the first proposal locally seen. Do `proposals.append(winning_proposal)`. - return proposals[-1] # stub -``` - -```python -def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[Root]: - return [hash_tree_root(proposal.message.body) for proposal in proposals] -``` - -```python -def get_proposal_choices_at_slot(beacon_state: BeaconState, - shard_state: ShardState, - slot: Slot, - shard: Shard, - shard_blocks: Sequence[SignedShardBlock], - validate_signature: bool=True) -> Sequence[SignedShardBlock]: - """ - Return the valid shard blocks at the given ``slot``. - Note that this function doesn't change the state. - """ - choices = [] - shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] - for block in shard_blocks_at_slot: - try: - # Verify block message and signature - # TODO these validations should have been checked upon receiving shard blocks. - assert verify_shard_block_message(beacon_state, shard_state, block.message, slot, shard) - if validate_signature: - assert verify_shard_block_signature(beacon_state, block) - - shard_state = get_post_shard_state(beacon_state, shard_state, block.message) - except Exception: - pass # TODO: throw error in the test helper - else: - choices.append(block) - return choices -``` - -```python -def get_proposal_at_slot(beacon_state: BeaconState, - shard_state: ShardState, - slot: Shard, - shard: Shard, - shard_blocks: Sequence[SignedShardBlock], - validate_signature: bool=True) -> Tuple[SignedShardBlock, ShardState]: - """ - Return ``proposal``, ``shard_state`` of the given ``slot``. - Note that this function doesn't change the state. - """ - choices = get_proposal_choices_at_slot( - beacon_state=beacon_state, - shard_state=shard_state, - slot=slot, - shard=shard, - shard_blocks=shard_blocks, - validate_signature=validate_signature, - ) - if len(choices) == 0: - block = ShardBlock(slot=slot) - proposal = SignedShardBlock(message=block) - elif len(choices) == 1: - proposal = choices[0] - else: - proposal = get_winning_proposal(beacon_state, choices) - - # Apply state transition - shard_state = get_post_shard_state(beacon_state, shard_state, proposal.message) - - return proposal, shard_state -``` - -```python -def get_shard_state_transition_result( - beacon_state: BeaconState, - shard: Shard, - shard_blocks: Sequence[SignedShardBlock], - validate_signature: bool=True, -) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: - proposals = [] - shard_states = [] - shard_state = beacon_state.shard_states[shard] - for slot in get_offset_slots(beacon_state, shard): - proposal, shard_state = get_proposal_at_slot( - beacon_state=beacon_state, - shard_state=shard_state, - slot=slot, - shard=shard, - shard_blocks=shard_blocks, - validate_signature=validate_signature, - ) - shard_states.append(shard_state) - proposals.append(proposal) - - shard_data_roots = compute_shard_body_roots(proposals) - - return proposals, shard_states, shard_data_roots -``` - -### Make attestations - -Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks` since the latest successful crosslink for `shard` into the beacon chain. Let `beacon_state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. - -```python -def get_shard_transition(beacon_state: BeaconState, - shard: Shard, - shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: - offset_slots = get_offset_slots(beacon_state, shard) - proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks) - - shard_block_lengths = [] - proposer_signatures = [] - for proposal in proposals: - shard_block_lengths.append(len(proposal.message.body)) - if proposal.signature != NO_SIGNATURE: - proposer_signatures.append(proposal.signature) - - if len(proposer_signatures) > 0: - proposer_signature_aggregate = bls.Aggregate(proposer_signatures) - else: - proposer_signature_aggregate = NO_SIGNATURE - - return ShardTransition( - start_slot=offset_slots[0], - shard_block_lengths=shard_block_lengths, - shard_data_roots=shard_data_roots, - shard_states=shard_states, - proposer_signature_aggregate=proposer_signature_aggregate, - ) -``` diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index a26987a342..c5d1cd8688 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -189,7 +189,7 @@ def get_best_light_client_aggregate(block: BeaconBlock, aggregates: Sequence[LightClientVote]) -> LightClientVote: viable_aggregates = [ aggregate for aggregate in aggregates - if aggregate.slot == get_previous_slot(block.slot) and aggregate.beacon_block_root == block.parent_root + if aggregate.slot == compute_previous_slot(block.slot) and aggregate.beacon_block_root == block.parent_root ] return max( @@ -242,7 +242,7 @@ class FullAttestation(Container): Note the timing of when to create/broadcast is altered from Phase 1. -A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid `BeaconBlock` from the expected beacon block proposer and a valid `ShardBlock` for the expected shard block porposer for the assigned `slot` or (b) one-half of the `slot` has transpired (`SECONDS_PER_SLOT / 2` seconds after the start of `slot`) -- whichever comes _first_. +A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid `BeaconBlock` from the expected beacon block proposer and a valid `ShardBlock` for the expected shard block proposer for the assigned `slot` or (b) one-half of the `slot` has transpired (`SECONDS_PER_SLOT / 2` seconds after the start of `slot`) -- whichever comes _first_. #### Attestation data @@ -251,6 +251,9 @@ A validator should create and broadcast the `attestation` to the associated atte - Let `head_block` be the result of running the fork choice during the assigned slot. - Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. - Let `head_shard_block` be the result of running the fork choice on the assigned shard chain during the assigned slot. +- Let `shard_blocks` be the shard blocks in the chain starting immediately _after_ the most recent crosslink (`head_state.shard_transitions[shard].latest_block_root`) up to the `head_shard_block`. + +*Note*: We assume that the fork choice only follows branches with valid `offset_slots` with respect to the most recent beacon state shard transition for the queried shard. ##### Head shard root @@ -258,17 +261,57 @@ Set `attestation_data.head_shard_root = hash_tree_root(head_shard_block)`. ##### Shard transition -Set `shard_transition` to the value returned by `get_shard_transition()`. +Set `shard_transition` to the value returned by `get_shard_transition(head_state, shard, shard_blocks)`. ```python -def get_shard_transition(state: BeaconState, +def get_shard_state_transition_result( + beacon_state: BeaconState, + shard: Shard, + shard_blocks: Sequence[SignedShardBlock], + validate_signature: bool=True, +) -> Tuple[Sequence[ShardState], Sequence[Root], Sequence[uint64]]: + shard_states = [] + shard_data_roots = [] + shard_block_lengths = [] + + shard_state = beacon_state.shard_states[shard] + shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks] + for slot in get_offset_slots(beacon_state, shard): + if slot in shard_block_slots: + shard_block = shard_blocks[shard_block_slots.index(slot)] + shard_data_roots.append(hash_tree_root(shard_block.message.body)) + else: + shard_block = SignedShardBlock(message=ShardBlock(slot=slot)) + shard_data_roots.append(Root()) + shard_state = get_post_shard_state(beacon_state, shard_state, shard_block.message) + shard_states.append(shard_state) + shard_block_lengths.append(len(shard_block.message.body)) + + return shard_states, shard_data_roots, shard_block_lengths +``` + +```python +def get_shard_transition(beacon_state: BeaconState, shard: Shard, - shard_blocks: Sequence[ShardBlockWrapper]) -> ShardTransition: - """ - latest_shard_slot = get_latest_slot_for_shard(state, shard) - offset_slots = [Slot(latest_shard_slot + x) for x in SHARD_BLOCK_OFFSETS if latest_shard_slot + x <= state.slot] - """ - return ShardTransition() + shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: + offset_slots = get_offset_slots(beacon_state, shard) + shard_states, shard_data_roots, shard_block_lengths = ( + get_shard_state_transition_result(beacon_state, shard, shard_blocks) + ) + + if len(shard_blocks) > 0: + proposer_signatures = [shard_block.signature for shard_block in shard_blocks] + proposer_signature_aggregate = bls.Aggregate(proposer_signatures) + else: + proposer_signature_aggregate = NO_SIGNATURE + + return ShardTransition( + start_slot=offset_slots[0], + shard_block_lengths=shard_block_lengths, + shard_data_roots=shard_data_roots, + shard_states=shard_states, + proposer_signature_aggregate=proposer_signature_aggregate, + ) ``` #### Construct attestation @@ -292,10 +335,25 @@ Set `attestation.signature = attestation_signature` where `attestation_signature ```python def get_attestation_signature(state: BeaconState, - attestation_data: AttestationData, - cb_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION], + attestation: Attestation, privkey: int) -> BLSSignature: - pass + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) + attestation_data_root = hash_tree_root(attestation.data) + index_in_committee = attestation.aggregation_bits.index(True) + signatures = [] + for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): + custody_bit = custody_bits[index_in_committee] + signing_root = compute_signing_root( + AttestationCustodyBitWrapper( + attestation_data_root=attestation_data_root, + block_index=block_index, + bit=custody_bit, + ), + domain, + ) + signatures.append(bls.Sign(privkey, signing_root)) + + return bls.Aggregate(signatures) ``` ### Light client committee diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index 1dfa0e4d0c..26affd579e 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -1,5 +1,11 @@ -from eth2spec.test.context import spec_state_test, always_bls, with_all_phases -from eth2spec.test.helpers.attestations import build_attestation_data +from random import Random + +from eth2spec.test.context import ( + spec_state_test, + always_bls, with_phases, with_all_phases, with_all_phases_except, + PHASE0, +) +from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.keys import privkeys, pubkeys @@ -317,18 +323,19 @@ def test_get_block_signature(spec, state): # Attesting -@with_all_phases +@with_phases([PHASE0]) @spec_state_test @always_bls -def test_get_attestation_signature(spec, state): +def test_get_attestation_signature_phase0(spec, state): privkey = privkeys[0] pubkey = pubkeys[0] - attestation_data = spec.AttestationData(slot=10) - domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) + attestation = get_valid_attestation(spec, state, signed=False) + domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) + run_get_signature_test( spec=spec, state=state, - obj=attestation_data, + obj=attestation.data, domain=domain, get_signature_fn=spec.get_attestation_signature, privkey=privkey, @@ -336,6 +343,28 @@ def test_get_attestation_signature(spec, state): ) +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_get_attestation_signature_phase1plus(spec, state): + privkey = privkeys[0] + + def single_participant(comm): + rng = Random(1100) + return rng.sample(comm, 1) + + attestation = get_valid_attestation(spec, state, filter_participant_set=single_participant, signed=False) + indexed_attestation = spec.get_indexed_attestation(state, attestation) + + assert indexed_attestation.attestation.aggregation_bits.count(True) == 1 + + # Cannot use normal `run_get_signature_test` due to complex signature type + index_in_committee = indexed_attestation.attestation.aggregation_bits.index(True) + privkey = privkeys[indexed_attestation.committee[index_in_committee]] + attestation.signature = spec.get_attestation_signature(state, attestation, privkey) + assert spec.verify_attestation_custody(state, spec.get_indexed_attestation(state, attestation)) + + # Attestation aggregation @@ -363,7 +392,7 @@ def test_get_slot_signature(spec, state): @always_bls def test_is_aggregator(spec, state): # TODO: we can test the probabilistic result against `TARGET_AGGREGATORS_PER_COMMITTEE` - # if we have more validators and larger committeee size + # if we have more validators and larger committee size slot = state.slot committee_index = 0 has_aggregator = False @@ -377,7 +406,7 @@ def test_is_aggregator(spec, state): assert has_aggregator -@with_all_phases +@with_phases([PHASE0]) @spec_state_test @always_bls def test_get_aggregate_signature(spec, state): From c0108afe7713ac10542c478b8d07514bdad42c3b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 05:06:04 +0800 Subject: [PATCH 044/236] Use shard_block.slot to get seed for proposer selection --- specs/phase1/beacon-chain.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 484b24cf17..fccb93f552 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -538,7 +538,8 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque ```python def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard) - r = bytes_to_int(get_seed(beacon_state, get_current_epoch(beacon_state), DOMAIN_SHARD_COMMITTEE)[:8]) + epoch = compute_epoch_at_slot(slot) + r = bytes_to_int(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE)[:8]) return committee[r % len(committee)] ``` From d3445217416ad3a8f73dbf45e4669970d063682a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 05:30:13 +0800 Subject: [PATCH 045/236] Bugfix: should set `shard` for empty proposal --- specs/phase1/shard-transition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index e6221a9805..5e8616568a 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -221,7 +221,7 @@ def get_proposal_at_slot(beacon_state: BeaconState, validate_signature=validate_signature, ) if len(choices) == 0: - block = ShardBlock(slot=slot) + block = ShardBlock(slot=slot, shard=shard) proposal = SignedShardBlock(message=block) elif len(choices) == 1: proposal = choices[0] From 26aae40941aac98d731655a475e3085e777c0110 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 05:31:53 +0800 Subject: [PATCH 046/236] Use epoch of the shard_block.slot for generating seed --- specs/phase1/beacon-chain.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 484b24cf17..fccb93f552 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -538,7 +538,8 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque ```python def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard) - r = bytes_to_int(get_seed(beacon_state, get_current_epoch(beacon_state), DOMAIN_SHARD_COMMITTEE)[:8]) + epoch = compute_epoch_at_slot(slot) + r = bytes_to_int(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE)[:8]) return committee[r % len(committee)] ``` From 376c83619024caee0dfd5f1c7da552ca78cedc10 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 4 Jun 2020 12:49:22 +1000 Subject: [PATCH 047/236] Clarify proposer slashing gossip conditions --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 9949db66f3..767748e891 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -263,7 +263,7 @@ Additional global topics are used to propagate lower frequency validator message - _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`. - _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation. - `proposer_slashing` - This topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. The following validations MUST pass before forwarding the `proposer_slashing` on to the network - - _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.index`. + - _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.header_1.proposer_index`. - _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation. - `attester_slashing` - This topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. - _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation has not yet been seen in any prior `attester_slashing` (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). From 8afb93f5a36adac5ef312ee3fa4678632df2b6c4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 11:19:04 +0800 Subject: [PATCH 048/236] Add `shard_block.slot` to seed --- specs/phase1/beacon-chain.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fccb93f552..4719d2e6f7 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -537,9 +537,13 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque ```python def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: - committee = get_shard_committee(beacon_state, compute_epoch_at_slot(slot), shard) + """ + Return the proposer's index of shard block at ``slot``. + """ epoch = compute_epoch_at_slot(slot) - r = bytes_to_int(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE)[:8]) + committee = get_shard_committee(beacon_state, epoch, shard) + seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE) + int_to_bytes(slot, length=8)) + r = bytes_to_int(seed[:8]) return committee[r % len(committee)] ``` From c2b7ff7422e449dc2486261adad6196259015579 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 4 Jun 2020 16:13:49 +1000 Subject: [PATCH 049/236] Re-clarify proposer slashing check Fixes a typo from #1871 --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 767748e891..bdf0919e03 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -263,7 +263,7 @@ Additional global topics are used to propagate lower frequency validator message - _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`. - _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation. - `proposer_slashing` - This topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. The following validations MUST pass before forwarding the `proposer_slashing` on to the network - - _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.header_1.proposer_index`. + - _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. - _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation. - `attester_slashing` - This topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. - _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation has not yet been seen in any prior `attester_slashing` (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). From 52de25048a20e79ee618396af17f4277c6347f2f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 09:35:09 -0600 Subject: [PATCH 050/236] fix lihth client refs in val guide --- specs/phase1/validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index c5d1cd8688..243c21014d 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -480,7 +480,7 @@ First, `light_aggregate_and_proof = get_light_aggregate_and_proof(state, validat ```python def get_light_aggregate_and_proof(state: BeaconState, aggregator_index: ValidatorIndex, - aggregate: Attestation, + aggregate: LightClientVote, privkey: int) -> LightAggregateAndProof: return LightAggregateAndProof( aggregator_index=aggregator_index, @@ -506,7 +506,7 @@ def get_light_aggregate_and_proof_signature(state: BeaconState, ```python class LightAggregateAndProof(Container): aggregator_index: ValidatorIndex - aggregate: Attestation + aggregate: LightClientVote selection_proof: BLSSignature ``` From 7e44456be543193b3c4848faead8ea9f7baa1583 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 09:50:49 -0600 Subject: [PATCH 051/236] mod compute_subnet_for_attestation to be usable for lookahead --- specs/phase0/p2p-interface.md | 4 ++-- specs/phase0/validator.md | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index bdf0919e03..6c4cdd2a68 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -275,7 +275,7 @@ Additional global topics are used to propagate lower frequency validator message Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are: - `beacon_attestation_{subnet_id}` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet. - - _[REJECT]_ The attestation is for the correct subnet (i.e. `compute_subnet_for_attestation(state, attestation) == subnet_id`). + - _[REJECT]_ The attestation is for the correct subnet (i.e. `compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index) == subnet_id`). - _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1`). - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. @@ -286,7 +286,7 @@ Attestation subnets are used to propagate unaggregated attestations to subsectio Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. `beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1. -Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(state, attestation)}` as `Attestation`s. +Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index)}` as `Attestation`s. Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `AggregateAndProof`s. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 5e8ddc9776..35c52f3468 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -199,8 +199,8 @@ The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahe Specifically a validator should: * Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments. -* Find peers of the pubsub topic `committee_index{committee_index % ATTESTATION_SUBNET_COUNT}_beacon_attestation`. - * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][committee_index % ATTESTATION_SUBNET_COUNT] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. +* Find peers of the pubsub topic `beacon_attestation_{compute_subnet_for_attestation(state, slot, committee_index)}`. + * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][compute_subnet_for_attestation(state, slot, committee_index)] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. * If the validator is assigned to be an aggregator for the slot (see `is_aggregator()`), then subscribe to the topic. *Note*: If the validator is _not_ assigned to be an aggregator, the validator only needs sufficient number of peers on the topic to be able to publish messages. The validator does not need to _subscribe_ and listen to all messages on the topic. @@ -425,18 +425,18 @@ def get_attestation_signature(state: BeaconState, attestation_data: AttestationD #### Broadcast attestation -Finally, the validator broadcasts `attestation` to the associated attestation subnet -- the `beacon_attestation_{compute_subnet_for_attestation(state, attestation)}` pubsub topic. +Finally, the validator broadcasts `attestation` to the associated attestation subnet -- the `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.committee_index)}` pubsub topic. ```python -def compute_subnet_for_attestation(state: BeaconState, attestation: Attestation) -> uint64: +def compute_subnet_for_attestation(state: BeaconState, slot: Slot, committee_index: CommitteeIndex) -> uint64: """ Compute the correct subnet for an attestation for Phase 0. Note, this mimics expected Phase 1 behavior where attestations will be mapped to their shard subnet. """ - slots_since_epoch_start = attestation.data.slot % SLOTS_PER_EPOCH - committees_since_epoch_start = get_committee_count_at_slot(state, attestation.data.slot) * slots_since_epoch_start + slots_since_epoch_start = slot % SLOTS_PER_EPOCH + committees_since_epoch_start = get_committee_count_at_slot(state, slot) * slots_since_epoch_start - return (committees_since_epoch_start + attestation.data.index) % ATTESTATION_SUBNET_COUNT + return (committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT ``` ### Attestation aggregation From 8e5c98ef3c661aa0b10c5ab4e6b050bb455c0957 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 12:01:53 -0600 Subject: [PATCH 052/236] PR feedback --- specs/phase1/validator.md | 43 +++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 243c21014d..21f9c600a5 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -131,14 +131,16 @@ Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./custody Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot. Specifically: -* Call `shards, winning_roots = get_successful_shard_transitions(state, block.slot, attestations)` +* Call `shards, winning_roots = get_shard_winning_roots(state, block.slot, attestations)` * Let `full_transitions` be a dictionary mapping from the `shard_transition_root`s found in `attestations` to the corresponding full `ShardTransition` * Then for each `shard` and `winning_root` in `zip(shards, winning_roots)` set `shard_transitions[shard] = full_transitions[winning_root]` +*Note*: The `state` passed into `get_shard_winning_roots` must be transitioned into the epoch of `block.slot` to run accurately due to the internal use of `get_online_validator_indices`. + ```python -def get_successful_shard_transitions(state: BeaconState, - slot: Slot, - attestations: Attestation) -> Tuple[Sequence[Shard], Sequence[Root]]: +def get_shard_winning_roots(state: BeaconState, + slot: Slot, + attestations: sequence[Attestation]) -> Tuple[Sequence[Shard], Sequence[Root]]: shards = [] winning_roots = [] online_indices = get_online_validator_indices(state) @@ -223,7 +225,7 @@ class FullAttestationData(Container): source: Checkpoint target: Checkpoint # Current-slot shard block root - head_shard_root: Root + shard_head_root: Root # Full shard transition shard_transition: ShardTransition ``` @@ -246,57 +248,64 @@ A validator should create and broadcast the `attestation` to the associated atte #### Attestation data -`attestation_data` is constructed in the same manner as Phase 0 but uses `FullAttestationData` with the addition of two fields -- `head_shard_root` and `shard_transition`. +`attestation_data` is constructed in the same manner as Phase 0 but uses `FullAttestationData` with the addition of two fields -- `shard_head_root` and `shard_transition`. - Let `head_block` be the result of running the fork choice during the assigned slot. - Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. -- Let `head_shard_block` be the result of running the fork choice on the assigned shard chain during the assigned slot. -- Let `shard_blocks` be the shard blocks in the chain starting immediately _after_ the most recent crosslink (`head_state.shard_transitions[shard].latest_block_root`) up to the `head_shard_block`. +- Let `shard_head_block` be the result of running the fork choice on the assigned shard chain during the assigned slot. +- Let `shard_blocks` be the shard blocks in the chain starting immediately _after_ the most recent crosslink (`head_state.shard_transitions[shard].latest_block_root`) up to the `shard_head_block`. *Note*: We assume that the fork choice only follows branches with valid `offset_slots` with respect to the most recent beacon state shard transition for the queried shard. ##### Head shard root -Set `attestation_data.head_shard_root = hash_tree_root(head_shard_block)`. +Set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`. ##### Shard transition Set `shard_transition` to the value returned by `get_shard_transition(head_state, shard, shard_blocks)`. ```python -def get_shard_state_transition_result( +def get_shard_transition_fields( beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock], validate_signature: bool=True, -) -> Tuple[Sequence[ShardState], Sequence[Root], Sequence[uint64]]: +) -> Tuple[Sequence[uint64], Sequence[Root], Sequence[ShardState]]: shard_states = [] shard_data_roots = [] shard_block_lengths = [] shard_state = beacon_state.shard_states[shard] shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks] - for slot in get_offset_slots(beacon_state, shard): + offset_slots = compute_offset_slots( + get_latest_slot_for_shard(beacon_state, shard), + beacon_state.slot + 1, + ) + for slot in offset_slots: if slot in shard_block_slots: shard_block = shard_blocks[shard_block_slots.index(slot)] shard_data_roots.append(hash_tree_root(shard_block.message.body)) else: - shard_block = SignedShardBlock(message=ShardBlock(slot=slot)) + shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) shard_state = get_post_shard_state(beacon_state, shard_state, shard_block.message) shard_states.append(shard_state) shard_block_lengths.append(len(shard_block.message.body)) - return shard_states, shard_data_roots, shard_block_lengths + return shard_block_lengths, shard_data_roots, shard_states ``` ```python def get_shard_transition(beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: - offset_slots = get_offset_slots(beacon_state, shard) - shard_states, shard_data_roots, shard_block_lengths = ( - get_shard_state_transition_result(beacon_state, shard, shard_blocks) + offset_slots = compute_offset_slots( + get_latest_slot_for_shard(beacon_state, shard), + beacon_state.slot + 1, + ) + shard_block_lengths, shard_data_roots, shard_states = ( + get_shard_transition_fields(beacon_state, shard, shard_blocks) ) if len(shard_blocks) > 0: From 8a9ccc4f3448d5c06d0e98af62bb5086f9d1a0d8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 12:07:59 -0600 Subject: [PATCH 053/236] clarify attestations as coming from block.body when getting winning roots in validator guide --- specs/phase1/validator.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 21f9c600a5..935f34efb7 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -74,7 +74,7 @@ See constants from [Phase 0 validator guide](../phase0/validator.md#constants). | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT` | `2**2` (= 8) | validators | | +| `TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT` | `2**3` (= 8) | validators | | ## Becoming a validator @@ -131,7 +131,7 @@ Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./custody Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot. Specifically: -* Call `shards, winning_roots = get_shard_winning_roots(state, block.slot, attestations)` +* Call `shards, winning_roots = get_shard_winning_roots(state, block.slot, block.body.attestations)` * Let `full_transitions` be a dictionary mapping from the `shard_transition_root`s found in `attestations` to the corresponding full `ShardTransition` * Then for each `shard` and `winning_root` in `zip(shards, winning_roots)` set `shard_transitions[shard] = full_transitions[winning_root]` @@ -140,7 +140,7 @@ Specifically: ```python def get_shard_winning_roots(state: BeaconState, slot: Slot, - attestations: sequence[Attestation]) -> Tuple[Sequence[Shard], Sequence[Root]]: + attestations: Sequence[Attestation]) -> Tuple[Sequence[Shard], Sequence[Root]]: shards = [] winning_roots = [] online_indices = get_online_validator_indices(state) @@ -280,7 +280,7 @@ def get_shard_transition_fields( shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks] offset_slots = compute_offset_slots( get_latest_slot_for_shard(beacon_state, shard), - beacon_state.slot + 1, + Slot(beacon_state.slot + 1), ) for slot in offset_slots: if slot in shard_block_slots: @@ -302,7 +302,7 @@ def get_shard_transition(beacon_state: BeaconState, shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: offset_slots = compute_offset_slots( get_latest_slot_for_shard(beacon_state, shard), - beacon_state.slot + 1, + Slot(beacon_state.slot + 1), ) shard_block_lengths, shard_data_roots, shard_states = ( get_shard_transition_fields(beacon_state, shard, shard_blocks) From c9a53b8039cb2eada12cbb09ca1cd74509356549 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 05:33:01 +0800 Subject: [PATCH 054/236] WIP test case --- .../test/fork_choice/test_on_shard_head.py | 93 ++++++++++++------- .../eth2spec/test/helpers/shard_block.py | 26 ++++-- 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 5b4205e91d..1151d18d70 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -5,6 +5,7 @@ build_attestation_with_shard_transition, build_shard_block, build_shard_transitions_till_slot, + get_committee_index_of_shard, ) from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root from eth2spec.test.helpers.state import next_slot, state_transition_and_sign_block @@ -24,44 +25,65 @@ def run_on_shard_block(spec, store, shard_store, signed_block, valid=True): assert shard_store.blocks[hash_tree_root(signed_block.message)] == signed_block.message -def run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index): +def run_apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer): shard = shard_store.shard + committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) + has_shard_committee = committee_index is not None store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH # Create SignedShardBlock - body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE - target_len_offset_slot = 1 - shard_block = build_shard_block(spec, state, shard, body=body, signed=True) - shard_blocks = [shard_block] + # Check offsets + temp_state = state.copy() + next_slot(spec, temp_state) + offset_slots = spec.get_offset_slots(temp_state, shard) + if state.slot in offset_slots: + # Build block + body = b'\x56' * 4 + shard_head_root = spec.get_shard_head(store, shard_store) + shard_parent_state = shard_store.block_states[shard_head_root] + assert shard_parent_state.slot != state.slot + shard_block = build_shard_block( + spec, state, shard, + shard_parent_state=shard_parent_state, slot=state.slot, body=body, signed=True + ) + shard_blocks_buffer.append(shard_block) + run_on_shard_block(spec, store, shard_store, shard_block) + assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root() - # Attester creates `attestation` - # Use temporary next state to get ShardTransition of shard block - shard_transitions = build_shard_transitions_till_slot( - spec, - state, - shard_blocks={shard: shard_blocks}, - on_time_slot=state.slot + target_len_offset_slot, - ) - shard_transition = shard_transitions[shard] - attestation = build_attestation_with_shard_transition( - spec, - state, - index=committee_index, - on_time_slot=state.slot + target_len_offset_slot, - shard_transition=shard_transition, - ) - - # Propose beacon block at slot beacon_block = build_empty_block(spec, state, slot=state.slot + 1) - beacon_block.body.attestations = [attestation] - beacon_block.body.shard_transitions = shard_transitions + + # Attester creates `attestation` + if has_shard_committee and len(shard_blocks_buffer) > 0: + # Use temporary next state to get ShardTransition of shard block + shard_transitions = build_shard_transitions_till_slot( + spec, + state, + shard_blocks={shard: shard_blocks_buffer}, + on_time_slot=state.slot + 1, + ) + shard_transition = shard_transitions[shard] + + attestation = build_attestation_with_shard_transition( + spec, + state, + index=committee_index, + on_time_slot=state.slot + 1, + shard_transition=shard_transition, + ) + assert attestation.data.slot == state.slot + assert spec.get_shard(state, attestation) == shard + beacon_block.body.attestations = [attestation] + beacon_block.body.shard_transitions = shard_transitions + signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) - run_on_shard_block(spec, store, shard_store, shard_block) add_block_to_store(spec, store, signed_beacon_block) - assert spec.get_head(store) == beacon_block.hash_tree_root() - assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root() + + if has_shard_committee: + shard_blocks_buffer = [] # clear buffer + + return has_shard_committee, shard_blocks_buffer @with_all_phases_except([PHASE0]) @@ -69,16 +91,19 @@ def run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) def test_basic(spec, state): spec.PHASE_1_GENESIS_SLOT = 0 # FIXME: remove mocking state = spec.upgrade_to_phase1(state) - next_slot(spec, state) # Initialization store = spec.get_forkchoice_store(state) anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root - committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.Shard(1) shard_store = spec.get_forkchoice_shard_store(state, shard) - - run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) - run_apply_shard_and_beacon(spec, state, store, shard_store, committee_index) + shard_block_count = 2 + shard_blocks_buffer = [] + while shard_block_count > 0: + has_shard_committee, shard_blocks_buffer = run_apply_shard_and_beacon( + spec, state, store, shard_store, shard_blocks_buffer + ) + if has_shard_committee: + shard_block_count -= 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 58efada835..410213edd0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -23,19 +23,24 @@ def build_shard_block(spec, shard, slot=None, body=None, + shard_parent_state=None, signed=False): - shard_state = beacon_state.shard_states[shard] + if shard_parent_state is None: + shard_parent_state = beacon_state.shard_states[shard] + if slot is None: - slot = shard_state.slot + 1 + slot = shard_parent_state.slot + 1 if body is None: body = b'\x56' * 128 - proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) + temp_state = beacon_state.copy() + transition_to(spec, temp_state, slot) beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot) - + assert beacon_state == temp_state + proposer_index = spec.get_shard_proposer_index(temp_state, slot, shard) block = spec.ShardBlock( - shard_parent_root=shard_state.latest_block_root, + shard_parent_root=shard_parent_state.latest_block_root, beacon_parent_root=beacon_parent_root, slot=slot, shard=shard, @@ -59,7 +64,6 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): for shard, blocks in shard_blocks.items(): offset_slots = spec.get_offset_slots(temp_state, shard) len_offset_slots = len(offset_slots) - assert len_offset_slots == on_time_slot - state.shard_states[shard].slot - 1 shard_transition = spec.get_shard_transition(temp_state, shard, blocks) if len(blocks) > 0: shard_block_root = blocks[-1].message.hash_tree_root() @@ -84,3 +88,13 @@ def build_attestation_with_shard_transition(spec, state, index, on_time_slot, sh if shard_transition is not None: assert attestation.data.shard_transition_root == shard_transition.hash_tree_root() return attestation + + +def get_committee_index_of_shard(spec, state, slot, shard): # Optional[CommitteeIndex] + active_shard_count = spec.get_active_shard_count(state) + committee_count = spec.get_committee_count_at_slot(state, slot) + start_shard = spec.get_start_shard(state, slot) + for committee_index in range(committee_count): + if (start_shard + committee_index) % active_shard_count == shard: + return committee_index + return None From 727353c054f259aa81e7738099fc055722e9734c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 18:39:27 +0800 Subject: [PATCH 055/236] Verify shard_block.slot fits the expected offset_slots --- specs/phase1/shard-fork-choice.md | 9 +++++---- specs/phase1/shard-transition.md | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index fb98893ac4..b60edd948a 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -127,7 +127,6 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si # Check beacon parent exists assert shard_block.beacon_parent_root in store.block_states - beacon_state = store.block_states[shard_block.beacon_parent_root] # Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor) finalized_beacon_state = store.block_states[store.finalized_checkpoint.root] @@ -144,9 +143,11 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si shard_store.blocks[hash_tree_root(shard_block)] = shard_block # Check the block is valid and compute the post-state - verify_shard_block_message(beacon_state, pre_shard_state, shard_block, shard_block.slot, shard) - verify_shard_block_signature(beacon_state, signed_shard_block) - post_state = get_post_shard_state(beacon_state, pre_shard_state, shard_block) + beacon_head_root = get_head(store) + beacon_head_state = store.block_states[beacon_head_root] + assert verify_shard_block_message(beacon_head_state, pre_shard_state, shard_block, shard_block.slot, shard) + assert verify_shard_block_signature(beacon_head_state, signed_shard_block) + post_state = get_post_shard_state(beacon_head_state, pre_shard_state, shard_block) # Add new state for this block to the store shard_store.block_states[hash_tree_root(shard_block)] = post_state ``` diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 5e8616568a..6c4f652d2f 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -50,6 +50,9 @@ def verify_shard_block_message(beacon_state: BeaconState, shard: Shard) -> bool: assert block.shard_parent_root == shard_state.latest_block_root assert block.slot == slot + next_slot = Slot(beacon_state.slot + 1) + offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), next_slot) + assert slot in offset_slots assert block.shard == shard assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE From f8597d296545c6711d1c7cf8fe25d5dbb5b1987b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 20:31:54 +0800 Subject: [PATCH 056/236] Add `get_pendings_shard_blocks` --- specs/phase1/shard-fork-choice.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index b60edd948a..5bc2cbe4e7 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -16,6 +16,7 @@ - [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance) - [`get_shard_head`](#get_shard_head) - [`get_shard_ancestor`](#get_shard_ancestor) + - [`get_pendings_shard_blocks`](#get_pendings_shard_blocks) - [Handlers](#handlers) - [`on_shard_block`](#on_shard_block) @@ -108,6 +109,31 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: return root ``` +#### `get_pendings_shard_blocks` + +```python +def get_pendings_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]: + """ + Return the shard blocks branch that from shard head to beacon head. + """ + shard = shard_store.shard + + beacon_head_root = get_head(store) + beacon_head_state = store.block_states[beacon_head_root] + latest_shard_block_root = beacon_head_state.shard_states[shard].latest_block_root + + shard_head_root = get_shard_head(store, shard_store) + root = shard_head_root + shard_blocks = [] + while root != latest_shard_block_root: + shard_block = shard_store.blocks[root] + shard_blocks.append(shard_block) + root = shard_block.shard_parent_root + + shard_blocks.reverse() + return shard_blocks +``` + ### Handlers #### `on_shard_block` From ab42eee4c04818d58fbcea5ba83d1c5890d0c3b8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 20:32:31 +0800 Subject: [PATCH 057/236] Update shard fork choice rule to be able to handle mainnet config --- .../test/fork_choice/test_on_shard_head.py | 97 ++++++++++++------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 1151d18d70..7e94ddd8e7 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -1,6 +1,6 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import spec_state_test, with_all_phases_except, PHASE0 +from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls from eth2spec.test.helpers.shard_block import ( build_attestation_with_shard_transition, build_shard_block, @@ -8,7 +8,7 @@ get_committee_index_of_shard, ) from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root -from eth2spec.test.helpers.state import next_slot, state_transition_and_sign_block +from eth2spec.test.helpers.state import state_transition_and_sign_block from eth2spec.test.helpers.block import build_empty_block @@ -25,35 +25,51 @@ def run_on_shard_block(spec, store, shard_store, signed_block, valid=True): assert shard_store.blocks[hash_tree_root(signed_block.message)] == signed_block.message -def run_apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer): +def apply_shard_block(spec, store, shard_store, beacon_head_state, shard_blocks_buffer): shard = shard_store.shard - committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) - has_shard_committee = committee_index is not None + body = b'\x56' * 4 + shard_head_root = spec.get_shard_head(store, shard_store) + shard_parent_state = shard_store.block_states[shard_head_root] + assert shard_parent_state.slot != beacon_head_state.slot + shard_block = build_shard_block( + spec, beacon_head_state, shard, + shard_parent_state=shard_parent_state, slot=beacon_head_state.slot, body=body, signed=True + ) + shard_blocks_buffer.append(shard_block) + run_on_shard_block(spec, store, shard_store, shard_block) + assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root() + + +def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer): + pending_shard_blocks = [ + spec.SignedShardBlock(message=b) + for b in spec.get_pendings_shard_blocks(store, shard_store) + ] + assert pending_shard_blocks == shard_blocks_buffer + + +def is_in_offset_sets(spec, beacon_head_state, shard): + offset_slots = spec.compute_offset_slots( + beacon_head_state.shard_states[shard].slot, beacon_head_state.slot + 1 + ) + return beacon_head_state.slot in offset_slots + + +def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer): store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH - # Create SignedShardBlock - # Check offsets - temp_state = state.copy() - next_slot(spec, temp_state) - offset_slots = spec.get_offset_slots(temp_state, shard) - if state.slot in offset_slots: - # Build block - body = b'\x56' * 4 - shard_head_root = spec.get_shard_head(store, shard_store) - shard_parent_state = shard_store.block_states[shard_head_root] - assert shard_parent_state.slot != state.slot - shard_block = build_shard_block( - spec, state, shard, - shard_parent_state=shard_parent_state, slot=state.slot, body=body, signed=True - ) - shard_blocks_buffer.append(shard_block) - run_on_shard_block(spec, store, shard_store, shard_block) - assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root() + shard = shard_store.shard + committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) + has_shard_committee = committee_index is not None # has committee of `shard` at this slot + # On beacon blocks at `state.slot + 1` beacon_block = build_empty_block(spec, state, slot=state.slot + 1) - # Attester creates `attestation` + # If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block if has_shard_committee and len(shard_blocks_buffer) > 0: + # Sanity check `get_pendings_shard_blocks` function + check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer) + # Use temporary next state to get ShardTransition of shard block shard_transitions = build_shard_transitions_till_slot( spec, @@ -62,7 +78,6 @@ def run_apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buf on_time_slot=state.slot + 1, ) shard_transition = shard_transitions[shard] - attestation = build_attestation_with_shard_transition( spec, state, @@ -75,35 +90,47 @@ def run_apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buf beacon_block.body.attestations = [attestation] beacon_block.body.shard_transitions = shard_transitions - signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) + # Clear buffer + shard_blocks_buffer.clear() + signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) add_block_to_store(spec, store, signed_beacon_block) assert spec.get_head(store) == beacon_block.hash_tree_root() - if has_shard_committee: - shard_blocks_buffer = [] # clear buffer + # On shard block at updated `state.slot` + if is_in_offset_sets(spec, state, shard): + # The created shard block would be appended to `shard_blocks_buffer` + apply_shard_block(spec, store, shard_store, state, shard_blocks_buffer) - return has_shard_committee, shard_blocks_buffer + return has_shard_committee @with_all_phases_except([PHASE0]) @spec_state_test +@never_bls # Set to never_bls for testing `check_pending_shard_blocks` def test_basic(spec, state): - spec.PHASE_1_GENESIS_SLOT = 0 # FIXME: remove mocking + spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here state = spec.upgrade_to_phase1(state) + shard = spec.Shard(1) # Initialization store = spec.get_forkchoice_store(state) anchor_root = get_anchor_root(spec, state) assert spec.get_head(store) == anchor_root - shard = spec.Shard(1) shard_store = spec.get_forkchoice_shard_store(state, shard) - shard_block_count = 2 + shard_head_root = spec.get_shard_head(store, shard_store) + assert shard_head_root == state.shard_states[shard].latest_block_root + assert shard_store.block_states[shard_head_root].slot == 1 + assert shard_store.block_states[shard_head_root] == state.shard_states[shard] + + # For mainnet config, it's possible that only one committee of `shard` per epoch. + # we set this counter to test more rounds. + shard_committee_counter = 2 shard_blocks_buffer = [] - while shard_block_count > 0: - has_shard_committee, shard_blocks_buffer = run_apply_shard_and_beacon( + while shard_committee_counter > 0: + has_shard_committee = apply_shard_and_beacon( spec, state, store, shard_store, shard_blocks_buffer ) if has_shard_committee: - shard_block_count -= 1 + shard_committee_counter -= 1 From 6f9c290bfb97240a9f41370b026d066ed8df6917 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 20:40:09 +0800 Subject: [PATCH 058/236] Add TODO flag of latest message --- specs/phase1/shard-fork-choice.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 5bc2cbe4e7..a474c82140 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -61,6 +61,8 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro state.validators[i].effective_balance for i in active_indices if ( i in store.latest_messages + # TODO: check the latest message logic: currently, validator's previous vote of another shard + # would be ignored once their newer vote is accepted. Check if it makes sense. and store.latest_messages[i].shard == shard_store.shard and get_shard_ancestor( store, shard_store, store.latest_messages[i].root, shard_store.blocks[root].slot From a154d0c22b9a3175e74383e4df53e5b09f6df0fd Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 21:24:17 +0800 Subject: [PATCH 059/236] Fix typo --- specs/phase1/shard-fork-choice.md | 6 +++--- .../pyspec/eth2spec/test/fork_choice/test_on_shard_head.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index a474c82140..427b72784e 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -16,7 +16,7 @@ - [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance) - [`get_shard_head`](#get_shard_head) - [`get_shard_ancestor`](#get_shard_ancestor) - - [`get_pendings_shard_blocks`](#get_pendings_shard_blocks) + - [`get_pending_shard_blocks`](#get_pending_shard_blocks) - [Handlers](#handlers) - [`on_shard_block`](#on_shard_block) @@ -111,10 +111,10 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: return root ``` -#### `get_pendings_shard_blocks` +#### `get_pending_shard_blocks` ```python -def get_pendings_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]: +def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]: """ Return the shard blocks branch that from shard head to beacon head. """ diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 7e94ddd8e7..1ba15968ff 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -43,7 +43,7 @@ def apply_shard_block(spec, store, shard_store, beacon_head_state, shard_blocks_ def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer): pending_shard_blocks = [ spec.SignedShardBlock(message=b) - for b in spec.get_pendings_shard_blocks(store, shard_store) + for b in spec.get_pending_shard_blocks(store, shard_store) ] assert pending_shard_blocks == shard_blocks_buffer @@ -67,7 +67,7 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer) # If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block if has_shard_committee and len(shard_blocks_buffer) > 0: - # Sanity check `get_pendings_shard_blocks` function + # Sanity check `get_pending_shard_blocks` function check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer) # Use temporary next state to get ShardTransition of shard block @@ -129,6 +129,7 @@ def test_basic(spec, state): shard_committee_counter = 2 shard_blocks_buffer = [] while shard_committee_counter > 0: + print(f'state.slot', state.slot) has_shard_committee = apply_shard_and_beacon( spec, state, store, shard_store, shard_blocks_buffer ) From 2d4788fe7d032f473fe5d60d732c40505ad8f485 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 5 Jun 2020 16:19:25 +0800 Subject: [PATCH 060/236] Fix `verify_shard_block_message` Add check for `block.beacon_parent_root` per Terence's suggestion Update `get_shard_transition` 1. Disable verification: it will be fix in v-guide 2. Use `on_time_slot` to compute offset_slots Rework tests --- specs/phase1/shard-fork-choice.md | 11 ++-- specs/phase1/shard-transition.md | 66 +++++++++++-------- .../test/fork_choice/test_on_shard_head.py | 19 ++---- .../eth2spec/test/helpers/attestations.py | 2 +- .../eth2spec/test/helpers/shard_block.py | 36 +++------- .../test/helpers/shard_transitions.py | 5 +- .../test_process_shard_transition.py | 31 ++++----- .../test/phase_1/sanity/test_blocks.py | 56 ++++++---------- 8 files changed, 94 insertions(+), 132 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 427b72784e..844dbfb864 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -151,10 +151,11 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si # Check shard parent exists assert shard_block.shard_parent_root in shard_store.block_states - pre_shard_state = shard_store.block_states[shard_block.shard_parent_root] + shard_parent_state = shard_store.block_states[shard_block.shard_parent_root] # Check beacon parent exists assert shard_block.beacon_parent_root in store.block_states + beacon_parent_state = store.block_states[shard_block.beacon_parent_root] # Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor) finalized_beacon_state = store.block_states[store.finalized_checkpoint.root] @@ -171,11 +172,9 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si shard_store.blocks[hash_tree_root(shard_block)] = shard_block # Check the block is valid and compute the post-state - beacon_head_root = get_head(store) - beacon_head_state = store.block_states[beacon_head_root] - assert verify_shard_block_message(beacon_head_state, pre_shard_state, shard_block, shard_block.slot, shard) - assert verify_shard_block_signature(beacon_head_state, signed_shard_block) - post_state = get_post_shard_state(beacon_head_state, pre_shard_state, shard_block) + assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block, shard_block.slot, shard) + assert verify_shard_block_signature(beacon_parent_state, signed_shard_block) + post_state = get_post_shard_state(beacon_parent_state, shard_parent_state, shard_block) # Add new state for this block to the store shard_store.block_states[hash_tree_root(shard_block)] = post_state ``` diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 6c4f652d2f..8d75879f56 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -30,7 +30,7 @@ This document describes the shard transition function and fraud proofs as part o ### Misc ```python -def compute_shard_transition_digest(beacon_state: BeaconState, +def compute_shard_transition_digest(beacon_parent_state: BeaconState, shard_state: ShardState, beacon_parent_root: Root, shard_body_root: Root) -> Bytes32: @@ -44,17 +44,33 @@ def compute_shard_transition_digest(beacon_state: BeaconState, ```python def verify_shard_block_message(beacon_state: BeaconState, - shard_state: ShardState, + shard_parent_state: ShardState, block: ShardBlock, slot: Slot, - shard: Shard) -> bool: - assert block.shard_parent_root == shard_state.latest_block_root - assert block.slot == slot - next_slot = Slot(beacon_state.slot + 1) + shard: Shard, + beacon_parent_slot: Slot=None) -> bool: + # Check `shard_parent_root` field + assert block.shard_parent_root == shard_parent_state.latest_block_root + # Check `beacon_parent_root` field + if beacon_parent_slot is None: + beacon_parent_slot = beacon_state.slot + if beacon_parent_slot == beacon_state.slot: + beacon_parent_block_header = beacon_state.latest_block_header.copy() + if beacon_parent_block_header.state_root == Root(): + beacon_parent_block_header.state_root = hash_tree_root(beacon_state) + beacon_parent_root = hash_tree_root(beacon_parent_block_header) + else: + beacon_parent_root = get_block_root_at_slot(beacon_state, beacon_parent_slot) + assert block.beacon_parent_root == beacon_parent_root + # Check `slot` field + next_slot = Slot(slot + 1) offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), next_slot) assert slot in offset_slots + # Check `shard` field assert block.shard == shard + # Check `proposer_index` field assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) + # Check `body` field assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE return True ``` @@ -177,7 +193,7 @@ def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[ ```python def get_proposal_choices_at_slot(beacon_state: BeaconState, - shard_state: ShardState, + shard_parent_state: ShardState, slot: Slot, shard: Shard, shard_blocks: Sequence[SignedShardBlock], @@ -188,25 +204,16 @@ def get_proposal_choices_at_slot(beacon_state: BeaconState, """ choices = [] shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] + shard_state = shard_parent_state.copy() for block in shard_blocks_at_slot: - try: - # Verify block message and signature - # TODO these validations should have been checked upon receiving shard blocks. - assert verify_shard_block_message(beacon_state, shard_state, block.message, slot, shard) - if validate_signature: - assert verify_shard_block_signature(beacon_state, block) - - shard_state = get_post_shard_state(beacon_state, shard_state, block.message) - except Exception: - pass # TODO: throw error in the test helper - else: - choices.append(block) + shard_state = get_post_shard_state(beacon_state, shard_state, block.message) + choices.append(block) return choices ``` ```python def get_proposal_at_slot(beacon_state: BeaconState, - shard_state: ShardState, + shard_parent_state: ShardState, slot: Shard, shard: Shard, shard_blocks: Sequence[SignedShardBlock], @@ -217,7 +224,7 @@ def get_proposal_at_slot(beacon_state: BeaconState, """ choices = get_proposal_choices_at_slot( beacon_state=beacon_state, - shard_state=shard_state, + shard_parent_state=shard_parent_state, slot=slot, shard=shard, shard_blocks=shard_blocks, @@ -232,7 +239,7 @@ def get_proposal_at_slot(beacon_state: BeaconState, proposal = get_winning_proposal(beacon_state, choices) # Apply state transition - shard_state = get_post_shard_state(beacon_state, shard_state, proposal.message) + shard_state = get_post_shard_state(beacon_state, shard_parent_state, proposal.message) return proposal, shard_state ``` @@ -242,15 +249,17 @@ def get_shard_state_transition_result( beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock], + on_time_slot: Slot, validate_signature: bool=True, ) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: proposals = [] shard_states = [] shard_state = beacon_state.shard_states[shard] - for slot in get_offset_slots(beacon_state, shard): + offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), on_time_slot) + for slot in offset_slots: proposal, shard_state = get_proposal_at_slot( beacon_state=beacon_state, - shard_state=shard_state, + shard_parent_state=shard_state, slot=slot, shard=shard, shard_blocks=shard_blocks, @@ -271,9 +280,12 @@ Suppose you are a committee member on shard `shard` at slot `current_slot` and y ```python def get_shard_transition(beacon_state: BeaconState, shard: Shard, - shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: - offset_slots = get_offset_slots(beacon_state, shard) - proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks) + shard_blocks: Sequence[SignedShardBlock], + on_time_slot: Slot) -> ShardTransition: + offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), on_time_slot) + proposals, shard_states, shard_data_roots = get_shard_state_transition_result( + beacon_state, shard, shard_blocks, on_time_slot + ) shard_block_lengths = [] proposer_signatures = [] diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 1ba15968ff..ca79cfd23c 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -1,8 +1,8 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls +from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.shard_block import ( - build_attestation_with_shard_transition, build_shard_block, build_shard_transitions_till_slot, get_committee_index_of_shard, @@ -25,15 +25,15 @@ def run_on_shard_block(spec, store, shard_store, signed_block, valid=True): assert shard_store.blocks[hash_tree_root(signed_block.message)] == signed_block.message -def apply_shard_block(spec, store, shard_store, beacon_head_state, shard_blocks_buffer): +def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_blocks_buffer): shard = shard_store.shard body = b'\x56' * 4 shard_head_root = spec.get_shard_head(store, shard_store) shard_parent_state = shard_store.block_states[shard_head_root] - assert shard_parent_state.slot != beacon_head_state.slot + assert shard_parent_state.slot != beacon_parent_state.slot shard_block = build_shard_block( - spec, beacon_head_state, shard, - shard_parent_state=shard_parent_state, slot=beacon_head_state.slot, body=body, signed=True + spec, beacon_parent_state, shard, + shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True ) shard_blocks_buffer.append(shard_block) run_on_shard_block(spec, store, shard_store, shard_block) @@ -62,30 +62,26 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer) committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) has_shard_committee = committee_index is not None # has committee of `shard` at this slot - # On beacon blocks at `state.slot + 1` beacon_block = build_empty_block(spec, state, slot=state.slot + 1) # If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block if has_shard_committee and len(shard_blocks_buffer) > 0: # Sanity check `get_pending_shard_blocks` function check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer) - # Use temporary next state to get ShardTransition of shard block shard_transitions = build_shard_transitions_till_slot( spec, state, shard_blocks={shard: shard_blocks_buffer}, - on_time_slot=state.slot + 1, ) shard_transition = shard_transitions[shard] - attestation = build_attestation_with_shard_transition( + attestation = get_valid_on_time_attestation( spec, state, index=committee_index, - on_time_slot=state.slot + 1, shard_transition=shard_transition, + signed=False, ) - assert attestation.data.slot == state.slot assert spec.get_shard(state, attestation) == shard beacon_block.body.attestations = [attestation] beacon_block.body.shard_transitions = shard_transitions @@ -129,7 +125,6 @@ def test_basic(spec, state): shard_committee_counter = 2 shard_blocks_buffer = [] while shard_committee_counter > 0: - print(f'state.slot', state.slot) has_shard_committee = apply_shard_and_beacon( spec, state, store, shard_store, shard_blocks_buffer ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 1372b0654e..106069dd6c 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -87,7 +87,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t if on_time: temp_state = state.copy() next_slot(spec, temp_state) - shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=[]) + shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=[], on_time_slot=slot + 1) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 410213edd0..0a2ed67d2b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -1,6 +1,4 @@ -from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import get_state_and_beacon_parent_root_at_slot -from eth2spec.test.helpers.state import transition_to from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls @@ -34,11 +32,8 @@ def build_shard_block(spec, if body is None: body = b'\x56' * 128 - temp_state = beacon_state.copy() - transition_to(spec, temp_state, slot) beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot) - assert beacon_state == temp_state - proposer_index = spec.get_shard_proposer_index(temp_state, slot, shard) + proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) block = spec.ShardBlock( shard_parent_root=shard_parent_state.latest_block_root, beacon_parent_root=beacon_parent_root, @@ -57,14 +52,17 @@ def build_shard_block(spec, return signed_block -def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): - temp_state = state.copy() - transition_to(spec, temp_state, on_time_slot) +def build_shard_transitions_till_slot(spec, parent_beacon_state, shard_blocks): shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS + on_time_slot = parent_beacon_state.slot + 1 for shard, blocks in shard_blocks.items(): - offset_slots = spec.get_offset_slots(temp_state, shard) + offset_slots = spec.compute_offset_slots( + spec.get_latest_slot_for_shard(parent_beacon_state, shard), + on_time_slot, + ) len_offset_slots = len(offset_slots) - shard_transition = spec.get_shard_transition(temp_state, shard, blocks) + shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks, on_time_slot) + if len(blocks) > 0: shard_block_root = blocks[-1].message.hash_tree_root() assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root @@ -74,22 +72,6 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): return shard_transitions -def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition): - temp_state = state.copy() - transition_to(spec, temp_state, on_time_slot - 1) - attestation = get_valid_on_time_attestation( - spec, - temp_state, - index=index, - shard_transition=shard_transition, - signed=True, - ) - assert attestation.data.slot == temp_state.slot - if shard_transition is not None: - assert attestation.data.shard_transition_root == shard_transition.hash_tree_root() - return attestation - - def get_committee_index_of_shard(spec, state, slot, shard): # Optional[CommitteeIndex] active_shard_count = spec.get_active_shard_count(state) committee_count = spec.get_committee_count_at_slot(state, slot) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index abb5e7278e..8e62b2f277 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -1,5 +1,4 @@ from eth2spec.test.context import expect_assertion_error -from eth2spec.test.helpers.state import transition_to def run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=True): @@ -37,7 +36,5 @@ def get_shard_transition_of_committee(spec, state, committee_index, slot=None, s slot = state.slot shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - temp_state = state.copy() - transition_to(spec, temp_state, slot + 1) - shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=shard_blocks) + shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks, on_time_slot=slot + 1) return shard_transition diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py index 00ffbe0a80..dab4973dad 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py @@ -2,71 +2,64 @@ PHASE0, with_all_phases_except, spec_state_test, - always_bls, ) +from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing from eth2spec.test.helpers.shard_block import ( - build_attestation_with_shard_transition, build_shard_block, build_shard_transitions_till_slot, ) -from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot +from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_slot def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): state = transition_to_valid_shard_slot(spec, state) - init_slot = state.slot committee_index = spec.CommitteeIndex(0) shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 + transition_to(spec, state, state.slot + target_len_offset_slot) + assert state.shard_states[shard].slot == state.slot - target_len_offset_slot - 1 # Create SignedShardBlock body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE - shard_block = build_shard_block(spec, state, shard, body=body, signed=True) + shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) shard_blocks = [shard_block] - # Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot` shard_transitions = build_shard_transitions_till_slot( spec, state, shard_blocks={shard: shard_blocks}, - on_time_slot=state.slot + target_len_offset_slot, ) shard_transition = shard_transitions[shard] - # Create an attestation that would be included at beacon block `state.slot + target_len_offset_slot` - attestation = build_attestation_with_shard_transition( + attestation = get_valid_on_time_attestation( spec, state, index=committee_index, - on_time_slot=state.slot + target_len_offset_slot, shard_transition=shard_transition, + signed=False, ) + next_slot(spec, state) pre_gasprice = state.shard_states[shard].gasprice - - transition_to(spec, state, state.slot + target_len_offset_slot) pre_shard_state = state.shard_states[shard] - yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid) if valid: - # After state transition, - assert state.slot == init_slot + target_len_offset_slot shard_state = state.shard_states[shard] assert shard_state != pre_shard_state assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] - + assert shard_state.latest_block_root == shard_block.message.hash_tree_root() if target_len_offset_slot == 1: assert shard_state.gasprice > pre_gasprice @with_all_phases_except([PHASE0]) @spec_state_test -@always_bls def test_basic_crosslinks(spec, state): + # NOTE: this test is only for full crosslink (minimal config), not for mainnet yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True) @with_all_phases_except([PHASE0]) @spec_state_test -@always_bls def test_multiple_offset_slots(spec, state): - yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=3, valid=True) + # NOTE: this test is only for full crosslink (minimal config), not for mainnet + yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=2, valid=True) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 0175bd40da..828cd19d72 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -4,37 +4,40 @@ PHASE0, with_all_phases_except, spec_state_test, - always_bls, ) +from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.shard_block import ( - build_attestation_with_shard_transition, build_shard_block, build_shard_transitions_till_slot, ) -from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot +from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to -def run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index, valid=True): - shard_transitions = build_shard_transitions_till_slot( - spec, state, shard_blocks, on_time_slot=state.slot + target_len_offset_slot - ) +def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True): + transition_to(spec, state, state.slot + target_len_offset_slot) + + body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) + shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} + + shard_transitions = build_shard_transitions_till_slot(spec, state, shard_blocks) attestations = [ - build_attestation_with_shard_transition( + get_valid_on_time_attestation( spec, state, - on_time_slot=state.slot + target_len_offset_slot, index=committee_index, shard_transition=shard_transitions[shard], + signed=True, ) for shard in shard_blocks.keys() ] - # Propose beacon block at slot `x + 1` - beacon_block = build_empty_block(spec, state, slot=state.slot + target_len_offset_slot) + beacon_block = build_empty_block(spec, state, slot=state.slot + 1) beacon_block.body.attestations = attestations beacon_block.body.shard_transitions = shard_transitions + pre_gasprice = state.shard_states[shard].gasprice pre_shard_states = state.shard_states.copy() yield 'pre', state.copy() yield 'block', beacon_block @@ -52,17 +55,18 @@ def run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_off assert post_shard_state == shard_transitions[shard].shard_states[ len(shard_transitions[shard].shard_states) - 1 ] - assert beacon_block.slot == shard_transitions[shard].shard_states[0].slot + target_len_offset_slot assert post_shard_state.slot == state.slot - 1 if len(shard_blocks[shard]) == 0: # `latest_block_root` is the same assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root + if target_len_offset_slot == 1 and len(shard_blocks) > 0: + assert post_shard_state.gasprice > pre_gasprice @with_all_phases_except([PHASE0]) @spec_state_test -@always_bls def test_process_beacon_block_with_normal_shard_transition(spec, state): + # NOTE: this test is only for full crosslink (minimal config), not for mainnet state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 @@ -70,25 +74,13 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 - pre_gasprice = state.shard_states[shard].gasprice - - # Create SignedShardBlock at slot `shard_state.slot + 1` - body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE - shard_block = build_shard_block(spec, state, shard, body=body, signed=True) - shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} - - yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index) - - shard_state = state.shard_states[shard] - - if target_len_offset_slot == 1 and len(shard_blocks) > 0: - assert shard_state.gasprice > pre_gasprice + yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard) @with_all_phases_except([PHASE0]) @spec_state_test -@always_bls def test_process_beacon_block_with_empty_proposal_transition(spec, state): + # NOTE: this test is only for full crosslink (minimal config), not for mainnet state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 @@ -96,12 +88,4 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 - # No new shard block - shard_blocks = {} - - pre_gasprice = state.shard_states[shard].gasprice - - yield from run_beacon_block_with_shard_blocks(spec, state, shard_blocks, target_len_offset_slot, committee_index) - - if target_len_offset_slot == 1 and len(shard_blocks) > 0: - assert state.shard_states[shard].gasprice > pre_gasprice + yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard) From 2afa315cb3b1d8abcef3af2ef7df9046eb4f8f23 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 5 Jun 2020 21:49:50 +0800 Subject: [PATCH 061/236] clean leftover --- tests/core/pyspec/eth2spec/test/helpers/attestations.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 106069dd6c..ef90a71aaa 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -85,9 +85,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t # No shard transition -> no shard block shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) if on_time: - temp_state = state.copy() - next_slot(spec, temp_state) - shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=[], on_time_slot=slot + 1) + shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[], on_time_slot=slot + 1) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() From a71c0a5ccc4c74f5ae8c3c071aa47506aa5cc03a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 6 Jun 2020 02:35:46 +0800 Subject: [PATCH 062/236] Per #1704 discussion, remove `on_time_slot`: the given `beacon_state` should be transitioned. --- specs/phase1/shard-transition.md | 4 ++-- tests/core/pyspec/eth2spec/test/helpers/attestations.py | 6 ++---- tests/core/pyspec/eth2spec/test/helpers/shard_block.py | 2 +- .../core/pyspec/eth2spec/test/helpers/shard_transitions.py | 7 ++----- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 8d75879f56..191dbd1aaa 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -280,8 +280,8 @@ Suppose you are a committee member on shard `shard` at slot `current_slot` and y ```python def get_shard_transition(beacon_state: BeaconState, shard: Shard, - shard_blocks: Sequence[SignedShardBlock], - on_time_slot: Slot) -> ShardTransition: + shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: + on_time_slot = Slot(beacon_state.slot + 1) offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), on_time_slot) proposals, shard_states, shard_data_roots = get_shard_state_transition_result( beacon_state, shard, shard_blocks, on_time_slot diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index ef90a71aaa..1e0560405b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -85,7 +85,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t # No shard transition -> no shard block shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) if on_time: - shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[], on_time_slot=slot + 1) + shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[]) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() @@ -318,9 +318,7 @@ def next_epoch_with_attestations(spec, for index in range(committees_per_slot): if spec.fork == PHASE1: shard = spec.compute_shard_from_committee_index(post_state, index, slot_to_attest) - shard_transition = get_shard_transition_of_committee( - spec, post_state, index, slot=slot_to_attest - ) + shard_transition = get_shard_transition_of_committee(spec, post_state, index) block.body.shard_transitions[shard] = shard_transition else: shard_transition = None diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 0a2ed67d2b..e63096b925 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -61,7 +61,7 @@ def build_shard_transitions_till_slot(spec, parent_beacon_state, shard_blocks): on_time_slot, ) len_offset_slots = len(offset_slots) - shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks, on_time_slot) + shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks) if len(blocks) > 0: shard_block_root = blocks[-1].message.hash_tree_root() diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index 8e62b2f277..d10d1ee7bc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -28,13 +28,10 @@ def run_shard_transitions_processing(spec, state, shard_transitions, attestation yield 'post', state -def get_shard_transition_of_committee(spec, state, committee_index, slot=None, shard_blocks=None): +def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks=None): if shard_blocks is None: shard_blocks = [] - if slot is None: - slot = state.slot - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks, on_time_slot=slot + 1) + shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks) return shard_transition From 7f680dfca4ad4c5085859ee4cb380fb7971ee5d9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 14:06:39 -0600 Subject: [PATCH 063/236] fix tests --- .../pyspec/eth2spec/test/helpers/attestations.py | 4 +--- .../pyspec/eth2spec/test/helpers/shard_block.py | 13 ++++--------- .../eth2spec/test/helpers/shard_transitions.py | 5 +---- .../test_process_shard_transition.py | 14 +++++++++----- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 1372b0654e..48930b95de 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -85,9 +85,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t # No shard transition -> no shard block shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) if on_time: - temp_state = state.copy() - next_slot(spec, temp_state) - shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=[]) + shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[]) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 58efada835..20e9ca3b59 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -1,6 +1,5 @@ from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import get_state_and_beacon_parent_root_at_slot -from eth2spec.test.helpers.state import transition_to from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls @@ -53,14 +52,13 @@ def build_shard_block(spec, def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): - temp_state = state.copy() - transition_to(spec, temp_state, on_time_slot) shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS for shard, blocks in shard_blocks.items(): - offset_slots = spec.get_offset_slots(temp_state, shard) + offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), on_time_slot) len_offset_slots = len(offset_slots) + # TODO this is actually unsafe for long offset_slots assert len_offset_slots == on_time_slot - state.shard_states[shard].slot - 1 - shard_transition = spec.get_shard_transition(temp_state, shard, blocks) + shard_transition = spec.get_shard_transition(state, shard, blocks) if len(blocks) > 0: shard_block_root = blocks[-1].message.hash_tree_root() assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root @@ -71,16 +69,13 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition): - temp_state = state.copy() - transition_to(spec, temp_state, on_time_slot - 1) attestation = get_valid_on_time_attestation( spec, - temp_state, + state, index=index, shard_transition=shard_transition, signed=True, ) - assert attestation.data.slot == temp_state.slot if shard_transition is not None: assert attestation.data.shard_transition_root == shard_transition.hash_tree_root() return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index abb5e7278e..544d88523c 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -1,5 +1,4 @@ from eth2spec.test.context import expect_assertion_error -from eth2spec.test.helpers.state import transition_to def run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=True): @@ -37,7 +36,5 @@ def get_shard_transition_of_committee(spec, state, committee_index, slot=None, s slot = state.slot shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) - temp_state = state.copy() - transition_to(spec, temp_state, slot + 1) - shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=shard_blocks) + shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks) return shard_transition diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py index 00ffbe0a80..959eaa2d80 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py @@ -17,19 +17,23 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): state = transition_to_valid_shard_slot(spec, state) init_slot = state.slot committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) - assert state.shard_states[shard].slot == state.slot - 1 + shard_slot = state.slot + target_len_offset_slot - 1 + shard = spec.compute_shard_from_committee_index(state, committee_index, shard_slot) + assert state.shard_states[shard].slot == init_slot - 1 # Create SignedShardBlock body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE shard_block = build_shard_block(spec, state, shard, body=body, signed=True) shard_blocks = [shard_block] + + # Transition state latest shard slot + transition_to(spec, state, shard_slot) # Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot` shard_transitions = build_shard_transitions_till_slot( spec, state, shard_blocks={shard: shard_blocks}, - on_time_slot=state.slot + target_len_offset_slot, + on_time_slot=init_slot + target_len_offset_slot, ) shard_transition = shard_transitions[shard] # Create an attestation that would be included at beacon block `state.slot + target_len_offset_slot` @@ -37,12 +41,12 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): spec, state, index=committee_index, - on_time_slot=state.slot + target_len_offset_slot, + on_time_slot=init_slot + target_len_offset_slot, shard_transition=shard_transition, ) pre_gasprice = state.shard_states[shard].gasprice - transition_to(spec, state, state.slot + target_len_offset_slot) + transition_to(spec, state, init_slot + target_len_offset_slot) pre_shard_state = state.shard_states[shard] yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid) From a4cc189f2b34b9e99d53df32dc24b383d3b4d3bd Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 6 Jun 2020 05:19:46 +0800 Subject: [PATCH 064/236] Apply PR feedback from Danny --- specs/phase1/shard-fork-choice.md | 10 +-- specs/phase1/shard-transition.md | 63 ++++--------------- .../test/fork_choice/test_on_shard_head.py | 4 +- 3 files changed, 21 insertions(+), 56 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 844dbfb864..b1fc3080ee 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -46,7 +46,7 @@ class ShardStore: def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore: return ShardStore( shard=shard, - blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot)}, + blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot, shard=shard)}, block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]}, ) ``` @@ -168,13 +168,15 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root ) - # Add new block to the store - shard_store.blocks[hash_tree_root(shard_block)] = shard_block - # Check the block is valid and compute the post-state assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block, shard_block.slot, shard) assert verify_shard_block_signature(beacon_parent_state, signed_shard_block) + post_state = get_post_shard_state(beacon_parent_state, shard_parent_state, shard_block) + + # Add new block to the store + shard_store.blocks[hash_tree_root(shard_block)] = shard_block + # Add new state for this block to the store shard_store.block_states[hash_tree_root(shard_block)] = post_state ``` diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 191dbd1aaa..fe3223933a 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -47,20 +47,14 @@ def verify_shard_block_message(beacon_state: BeaconState, shard_parent_state: ShardState, block: ShardBlock, slot: Slot, - shard: Shard, - beacon_parent_slot: Slot=None) -> bool: + shard: Shard) -> bool: # Check `shard_parent_root` field assert block.shard_parent_root == shard_parent_state.latest_block_root # Check `beacon_parent_root` field - if beacon_parent_slot is None: - beacon_parent_slot = beacon_state.slot - if beacon_parent_slot == beacon_state.slot: - beacon_parent_block_header = beacon_state.latest_block_header.copy() - if beacon_parent_block_header.state_root == Root(): - beacon_parent_block_header.state_root = hash_tree_root(beacon_state) - beacon_parent_root = hash_tree_root(beacon_parent_block_header) - else: - beacon_parent_root = get_block_root_at_slot(beacon_state, beacon_parent_slot) + beacon_parent_block_header = beacon_state.latest_block_header.copy() + if beacon_parent_block_header.state_root == Root(): + beacon_parent_block_header.state_root = hash_tree_root(beacon_state) + beacon_parent_root = hash_tree_root(beacon_parent_block_header) assert block.beacon_parent_root == beacon_parent_root # Check `slot` field next_slot = Slot(slot + 1) @@ -191,26 +185,6 @@ def compute_shard_body_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[ return [hash_tree_root(proposal.message.body) for proposal in proposals] ``` -```python -def get_proposal_choices_at_slot(beacon_state: BeaconState, - shard_parent_state: ShardState, - slot: Slot, - shard: Shard, - shard_blocks: Sequence[SignedShardBlock], - validate_signature: bool=True) -> Sequence[SignedShardBlock]: - """ - Return the valid shard blocks at the given ``slot``. - Note that this function doesn't change the state. - """ - choices = [] - shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] - shard_state = shard_parent_state.copy() - for block in shard_blocks_at_slot: - shard_state = get_post_shard_state(beacon_state, shard_state, block.message) - choices.append(block) - return choices -``` - ```python def get_proposal_at_slot(beacon_state: BeaconState, shard_parent_state: ShardState, @@ -222,21 +196,14 @@ def get_proposal_at_slot(beacon_state: BeaconState, Return ``proposal``, ``shard_state`` of the given ``slot``. Note that this function doesn't change the state. """ - choices = get_proposal_choices_at_slot( - beacon_state=beacon_state, - shard_parent_state=shard_parent_state, - slot=slot, - shard=shard, - shard_blocks=shard_blocks, - validate_signature=validate_signature, - ) - if len(choices) == 0: + shard_blocks = [block for block in shard_blocks if block.message.slot == slot] + if len(shard_blocks) == 0: block = ShardBlock(slot=slot, shard=shard) proposal = SignedShardBlock(message=block) - elif len(choices) == 1: - proposal = choices[0] + elif len(shard_blocks) == 1: + proposal = shard_blocks[0] else: - proposal = get_winning_proposal(beacon_state, choices) + proposal = get_winning_proposal(beacon_state, shard_blocks) # Apply state transition shard_state = get_post_shard_state(beacon_state, shard_parent_state, proposal.message) @@ -249,13 +216,12 @@ def get_shard_state_transition_result( beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock], - on_time_slot: Slot, validate_signature: bool=True, ) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: proposals = [] shard_states = [] shard_state = beacon_state.shard_states[shard] - offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), on_time_slot) + offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1)) for slot in offset_slots: proposal, shard_state = get_proposal_at_slot( beacon_state=beacon_state, @@ -281,11 +247,8 @@ Suppose you are a committee member on shard `shard` at slot `current_slot` and y def get_shard_transition(beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: - on_time_slot = Slot(beacon_state.slot + 1) - offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), on_time_slot) - proposals, shard_states, shard_data_roots = get_shard_state_transition_result( - beacon_state, shard, shard_blocks, on_time_slot - ) + offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1)) + proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks) shard_block_lengths = [] proposer_signatures = [] diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index ca79cfd23c..1a90429603 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -89,11 +89,11 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer) # Clear buffer shard_blocks_buffer.clear() - signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) + signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) # transition! add_block_to_store(spec, store, signed_beacon_block) assert spec.get_head(store) == beacon_block.hash_tree_root() - # On shard block at updated `state.slot` + # On shard block at transitioned `state.slot` if is_in_offset_sets(spec, state, shard): # The created shard block would be appended to `shard_blocks_buffer` apply_shard_block(spec, store, shard_store, state, shard_blocks_buffer) From ffeecfbca5b5994b4f34899b9fa3931526d49c80 Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Fri, 5 Jun 2020 20:26:11 -0700 Subject: [PATCH 065/236] Add test for compute_fork_digest --- .../test/validator/test_validator_unittest.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index 1dfa0e4d0c..0ac1e7b463 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -314,6 +314,19 @@ def test_get_block_signature(spec, state): ) +@with_all_phases +@spec_state_test +def test_compute_fork_digest(spec, state): + actual_fork_digest = spec.compute_fork_digest(state.fork.current_version, state.genesis_validators_root) + + expected_fork_data_root = spec.hash_tree_root( + spec.ForkData(current_version=state.fork.current_version, + genesis_validators_root=state.genesis_validators_root)) + expected_fork_digest = spec.ForkDigest(expected_fork_data_root[:4]) + + assert actual_fork_digest == expected_fork_digest + + # Attesting From 435505746cd13f336c0bad1c71829df7fd322ca2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 8 Jun 2020 17:12:46 +0800 Subject: [PATCH 066/236] PR feedback from Terence: fix `get_shard_latest_attesting_balance` Co-authored-by: terence tsao --- specs/phase1/shard-fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 427b72784e..5fef81868f 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -65,7 +65,7 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro # would be ignored once their newer vote is accepted. Check if it makes sense. and store.latest_messages[i].shard == shard_store.shard and get_shard_ancestor( - store, shard_store, store.latest_messages[i].root, shard_store.blocks[root].slot + store, shard_store, store.latest_messages[i].shard_root, shard_store.blocks[root].slot ) == root ) )) From 7e67aaeb35c6d8ffc4d8ced61f274161b6c04660 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 8 Jun 2020 18:15:14 +0800 Subject: [PATCH 067/236] Rename `build_shard_transitions_till_slot` to `get_shard_transitions` --- .../pyspec/eth2spec/test/fork_choice/test_on_shard_head.py | 4 ++-- tests/core/pyspec/eth2spec/test/helpers/shard_block.py | 2 +- .../phase_1/block_processing/test_process_shard_transition.py | 4 ++-- tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 1a90429603..24eeaedbee 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -4,7 +4,7 @@ from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.shard_block import ( build_shard_block, - build_shard_transitions_till_slot, + get_shard_transitions, get_committee_index_of_shard, ) from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root @@ -69,7 +69,7 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer) # Sanity check `get_pending_shard_blocks` function check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer) # Use temporary next state to get ShardTransition of shard block - shard_transitions = build_shard_transitions_till_slot( + shard_transitions = get_shard_transitions( spec, state, shard_blocks={shard: shard_blocks_buffer}, diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index e63096b925..f8b4a155f4 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -52,7 +52,7 @@ def build_shard_block(spec, return signed_block -def build_shard_transitions_till_slot(spec, parent_beacon_state, shard_blocks): +def get_shard_transitions(spec, parent_beacon_state, shard_blocks): shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS on_time_slot = parent_beacon_state.slot + 1 for shard, blocks in shard_blocks.items(): diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py index dab4973dad..e97cc90a8f 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py @@ -7,7 +7,7 @@ from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing from eth2spec.test.helpers.shard_block import ( build_shard_block, - build_shard_transitions_till_slot, + get_shard_transitions, ) from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_slot @@ -24,7 +24,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) shard_blocks = [shard_block] - shard_transitions = build_shard_transitions_till_slot( + shard_transitions = get_shard_transitions( spec, state, shard_blocks={shard: shard_blocks}, diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 828cd19d72..33b0beac7b 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -9,7 +9,7 @@ from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.shard_block import ( build_shard_block, - build_shard_transitions_till_slot, + get_shard_transitions, ) from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to @@ -21,7 +21,7 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} - shard_transitions = build_shard_transitions_till_slot(spec, state, shard_blocks) + shard_transitions = get_shard_transitions(spec, state, shard_blocks) attestations = [ get_valid_on_time_attestation( spec, From e03a970eaf88e82ade443ec37ac7451f5903966c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 8 Jun 2020 23:49:24 +0800 Subject: [PATCH 068/236] PR feedback from danny: simplify `verify_shard_block_message` params --- specs/phase1/shard-fork-choice.md | 2 +- specs/phase1/shard-transition.md | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index b1fc3080ee..6c431a68d0 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -169,7 +169,7 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si ) # Check the block is valid and compute the post-state - assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block, shard_block.slot, shard) + assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block) assert verify_shard_block_signature(beacon_parent_state, signed_shard_block) post_state = get_post_shard_state(beacon_parent_state, shard_parent_state, shard_block) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index fe3223933a..c62b059eeb 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -43,27 +43,26 @@ def compute_shard_transition_digest(beacon_parent_state: BeaconState, ### Shard block verification functions ```python -def verify_shard_block_message(beacon_state: BeaconState, +def verify_shard_block_message(beacon_parent_state: BeaconState, shard_parent_state: ShardState, - block: ShardBlock, - slot: Slot, - shard: Shard) -> bool: + block: ShardBlock) -> bool: # Check `shard_parent_root` field assert block.shard_parent_root == shard_parent_state.latest_block_root # Check `beacon_parent_root` field - beacon_parent_block_header = beacon_state.latest_block_header.copy() + beacon_parent_block_header = beacon_parent_state.latest_block_header.copy() if beacon_parent_block_header.state_root == Root(): - beacon_parent_block_header.state_root = hash_tree_root(beacon_state) + beacon_parent_block_header.state_root = hash_tree_root(beacon_parent_state) beacon_parent_root = hash_tree_root(beacon_parent_block_header) assert block.beacon_parent_root == beacon_parent_root # Check `slot` field - next_slot = Slot(slot + 1) - offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), next_slot) - assert slot in offset_slots + shard = block.shard + next_slot = Slot(block.slot + 1) + offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_parent_state, shard), next_slot) + assert block.slot in offset_slots # Check `shard` field assert block.shard == shard # Check `proposer_index` field - assert block.proposer_index == get_shard_proposer_index(beacon_state, slot, shard) + assert block.proposer_index == get_shard_proposer_index(beacon_parent_state, block.slot, shard) # Check `body` field assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE return True From 2d895e9388cd7364448fb357a769a9f03d7a5141 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 9 Jun 2020 00:13:27 +0800 Subject: [PATCH 069/236] PR feedback from danny --- specs/phase1/shard-fork-choice.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 61e4cd36f3..0607613e8d 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -76,18 +76,18 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro ```python def get_shard_head(store: Store, shard_store: ShardStore) -> Root: # Execute the LMD-GHOST fork choice - shard_blocks = shard_store.blocks - head_beacon_root = get_head(store) - head_shard_state = store.block_states[head_beacon_root].shard_states[shard_store.shard] - shard_head_root = head_shard_state.latest_block_root + beacon_head_root = get_head(store) + shard_head_state = store.block_states[beacon_head_root].shard_states[shard_store.shard] + shard_head_root = shard_head_state.latest_block_root + shard_blocks = { + root: shard_block for root, shard_block in shard_store.blocks.items() + if shard_block.slot > shard_head_state.slot + } while True: # Find the valid child block roots children = [ - root for root in shard_store.blocks.keys() - if ( - shard_blocks[root].shard_parent_root == shard_head_root - and shard_blocks[root].slot > head_shard_state.slot - ) + root for root, shard_block in shard_blocks.items() + if shard_block.shard_parent_root == shard_head_root ] if len(children) == 0: return shard_head_root @@ -107,7 +107,7 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: elif block.slot == slot: return root else: - # root is older than queried slot, thus a skip slot. Return earliest root prior to slot + # root is older than queried slot, thus a skip slot. Return most recent root prior to slot return root ``` @@ -116,7 +116,7 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: ```python def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]: """ - Return the shard blocks branch that from shard head to beacon head. + Return the canonical shard block branch that has not yet been crosslinked. """ shard = shard_store.shard From dacf86a5c0ab75bd966f9a3639607427cd876dd3 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 10 Jun 2020 02:43:59 +0800 Subject: [PATCH 070/236] Remove `transition_digest` --- specs/phase1/beacon-chain.md | 1 - specs/phase1/phase1-fork.md | 1 - specs/phase1/shard-fork-choice.md | 2 +- specs/phase1/shard-transition.md | 34 ++++++------------------------- specs/phase1/validator.md | 2 +- 5 files changed, 8 insertions(+), 32 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index d559ebcbdd..8ee6a1e833 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -349,7 +349,6 @@ class ShardBlockHeader(Container): class ShardState(Container): slot: Slot gasprice: Gwei - transition_digest: Bytes32 latest_block_root: Root ``` diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index d362ed6334..7a7423e103 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -104,7 +104,6 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: ShardState( slot=pre.slot, gasprice=MIN_GASPRICE, - transition_digest=Root(), latest_block_root=Root(), ) for i in range(INITIAL_ACTIVE_SHARDS) ), diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 0607613e8d..411ad9b6b7 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -172,7 +172,7 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block) assert verify_shard_block_signature(beacon_parent_state, signed_shard_block) - post_state = get_post_shard_state(beacon_parent_state, shard_parent_state, shard_block) + post_state = get_post_shard_state(shard_parent_state, shard_block) # Add new block to the store shard_store.blocks[hash_tree_root(shard_block)] = shard_block diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index ea19b813db..24c39aa3d2 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -10,7 +10,6 @@ - [Introduction](#introduction) - [Helper functions](#helper-functions) - - [Misc](#misc) - [Shard block verification functions](#shard-block-verification-functions) - [Shard state transition](#shard-state-transition) - [Fraud proofs](#fraud-proofs) @@ -24,19 +23,6 @@ This document describes the shard transition function and fraud proofs as part o ## Helper functions -### Misc - -```python -def compute_shard_transition_digest(beacon_parent_state: BeaconState, - shard_state: ShardState, - beacon_parent_root: Root, - shard_body_root: Root) -> Bytes32: - # TODO: use SSZ hash tree root - return hash( - hash_tree_root(shard_state) + beacon_parent_root + shard_body_root - ) -``` - ### Shard block verification functions ```python @@ -77,11 +63,10 @@ def verify_shard_block_signature(beacon_state: BeaconState, ## Shard state transition ```python -def shard_state_transition(beacon_state: BeaconState, - shard_state: ShardState, +def shard_state_transition(shard_state: ShardState, block: ShardBlock) -> None: """ - Update ``shard_state`` with shard ``block`` and ``beacon_state`. + Update ``shard_state`` with shard ``block``. """ shard_state.slot = block.slot prev_gasprice = shard_state.gasprice @@ -91,25 +76,18 @@ def shard_state_transition(beacon_state: BeaconState, else: latest_block_root = hash_tree_root(block) shard_state.latest_block_root = latest_block_root - shard_state.transition_digest = compute_shard_transition_digest( - beacon_state, - shard_state, - block.beacon_parent_root, - hash_tree_root(block.body), - ) ``` We have a pure function `get_post_shard_state` for describing the fraud proof verification and honest validator behavior. ```python -def get_post_shard_state(beacon_state: BeaconState, - shard_state: ShardState, +def get_post_shard_state(shard_state: ShardState, block: ShardBlock) -> ShardState: """ A pure function that returns a new post ShardState instead of modifying the given `shard_state`. """ post_state = shard_state.copy() - shard_state_transition(beacon_state, post_state, block) + shard_state_transition(post_state, block) return post_state ``` @@ -151,8 +129,8 @@ def is_valid_fraud_proof(beacon_state: BeaconState, else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - shard_state = get_post_shard_state(beacon_state, shard_state, block) - if shard_state.transition_digest != transition.shard_states[offset_index].transition_digest: + shard_state = get_post_shard_state(shard_state, block) + if shard_state != transition.shard_states[offset_index]: return True return False diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 935f34efb7..403a6efb3c 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -289,7 +289,7 @@ def get_shard_transition_fields( else: shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) - shard_state = get_post_shard_state(beacon_state, shard_state, shard_block.message) + shard_state = get_post_shard_state(shard_state, shard_block.message) shard_states.append(shard_state) shard_block_lengths.append(len(shard_block.message.body)) From f0f7bda2eaf19d40c280958a2c2d6c3fc6644c9b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 10 Jun 2020 02:44:09 +0800 Subject: [PATCH 071/236] Fix tests --- tests/core/pyspec/eth2spec/test/helpers/attestations.py | 2 +- tests/core/pyspec/eth2spec/test/helpers/shard_block.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 1e0560405b..2bfe63bd13 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -90,7 +90,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: - attestation_data.shard_head_root = state.shard_states[shard].transition_digest + attestation_data.shard_head_root = state.shard_states[shard].latest_block_root attestation_data.shard_transition_root = spec.Root() return attestation_data diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 15443e3860..ddf66f6c23 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -62,8 +62,6 @@ def get_shard_transitions(spec, parent_beacon_state, shard_blocks): on_time_slot, ) len_offset_slots = len(offset_slots) - # TODO this is actually unsafe for long offset_slots - assert len_offset_slots == on_time_slot - parent_beacon_state.shard_states[shard].slot - 1 shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks) if len(blocks) > 0: From ef11e924fa62d6e18ffc04df09ed6f56b753babb Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 9 Jun 2020 13:34:46 -0700 Subject: [PATCH 072/236] Update phase1 beacon-chain config table --- specs/phase1/beacon-chain.md | 52 ++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4719d2e6f7..6bc1d057a3 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -102,23 +102,47 @@ Configuration is not namespaced. Instead it is strictly an extension; ### Misc -| Name | Value | Unit | Duration | -| - | - | - | - | +| Name | Value | +| - | - | | `MAX_SHARDS` | `2**10` (= 1024) | -| `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 min | | `LIGHT_CLIENT_COMMITTEE_SIZE` | `2**7` (= 128) | +| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | + +### Shard block configs +| Name | Value | +| - | - | +| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | +| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | +| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | +| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | + +### Gwei values + +| Name | Value | +| - | - | +| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | +| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | + +### Initial values + +| Name | Value | +| - | - | +| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | + +### Time parameters + +| Name | Value | Unit | Duration | +| - | - | :-: | :-: | +| `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 mins | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | | -| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | | -| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | | -| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | | -| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | | -| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | | -| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | | -| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | -| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | -| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | -| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | | + +### Domain types + +| Name | Value | +| - | - | +| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | +| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | +| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | ## Updated containers From 0f6efcd50ddbd6800871f7a4bd781f1d87ac4dec Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 9 Jun 2020 15:38:40 -0700 Subject: [PATCH 073/236] Update toc --- specs/phase1/beacon-chain.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 6bc1d057a3..87de9d34ea 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -12,6 +12,11 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) + - [Shard block configs](#shard-block-configs) + - [Gwei values](#gwei-values) + - [Initial values](#initial-values) + - [Time parameters](#time-parameters) + - [Domain types](#domain-types) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) - [Extended `Attestation`](#extended-attestation) @@ -109,6 +114,7 @@ Configuration is not namespaced. Instead it is strictly an extension; | `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | ### Shard block configs + | Name | Value | | - | - | | `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | From 378d24948763af2a4492c2bee33dddf8cea6d94d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 10 Jun 2020 11:02:10 +1000 Subject: [PATCH 074/236] Avoid redundant call to get_ancestor --- specs/phase0/fork-choice.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index b9d8ecd3c3..a4d6b6cfad 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -371,15 +371,16 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: # Update finalized checkpoint if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: store.finalized_checkpoint = state.finalized_checkpoint - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - - # Update justified if new justified is later than store justified - # or if store justified is not in chain with finalized checkpoint - if ( - state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch - or get_ancestor(store, store.justified_checkpoint.root, finalized_slot) != store.finalized_checkpoint.root - ): - store.justified_checkpoint = state.current_justified_checkpoint + + if store.justified_checkpoint != state.current_justified_checkpoint: + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + # Update justified if new justified is later than store justified + # or if store justified is not in chain with finalized checkpoint + if ( + state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch + or get_ancestor(store, store.justified_checkpoint.root, finalized_slot) != store.finalized_checkpoint.root + ): + store.justified_checkpoint = state.current_justified_checkpoint ``` #### `on_attestation` From 29d968bb2e4054c408a11e2a2e1dd562ac60aa5c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 10 Jun 2020 15:09:40 +1000 Subject: [PATCH 075/236] Use parent_root for finalized chain check --- specs/phase0/fork-choice.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index b9d8ecd3c3..95142f80ce 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -347,17 +347,17 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: pre_state = store.block_states[block.parent_root].copy() # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. assert get_current_slot(store) >= block.slot - # Add new block to the store - store.blocks[hash_tree_root(block)] = block # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) assert block.slot > finalized_slot # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, hash_tree_root(block), finalized_slot) == store.finalized_checkpoint.root + assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root # Check the block is valid and compute the post-state state = state_transition(pre_state, signed_block, True) + # Add new block to the store + store.blocks[hash_tree_root(block)] = block # Add new state for this block to the store store.block_states[hash_tree_root(block)] = state From 479c40450dac7770434d2577499da0c5f63140ce Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 10 Jun 2020 18:16:26 +0800 Subject: [PATCH 076/236] Friendly lint fix --- specs/phase0/fork-choice.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index a4d6b6cfad..ad1dc540ca 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -371,14 +371,15 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: # Update finalized checkpoint if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: store.finalized_checkpoint = state.finalized_checkpoint - + if store.justified_checkpoint != state.current_justified_checkpoint: finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) # Update justified if new justified is later than store justified # or if store justified is not in chain with finalized checkpoint if ( state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch - or get_ancestor(store, store.justified_checkpoint.root, finalized_slot) != store.finalized_checkpoint.root + or get_ancestor( + store, store.justified_checkpoint.root, finalized_slot) != store.finalized_checkpoint.root ): store.justified_checkpoint = state.current_justified_checkpoint ``` From 1dc6b55617deb1aabd8801ad412740c9d6adc50d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 10 Jun 2020 09:40:34 -0500 Subject: [PATCH 077/236] rearrange fork choice condition for clarity --- specs/phase0/fork-choice.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index ad1dc540ca..132477ee8a 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -372,15 +372,17 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: store.finalized_checkpoint = state.finalized_checkpoint + # Potentially update justified if different from store if store.justified_checkpoint != state.current_justified_checkpoint: - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) # Update justified if new justified is later than store justified - # or if store justified is not in chain with finalized checkpoint - if ( - state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch - or get_ancestor( - store, store.justified_checkpoint.root, finalized_slot) != store.finalized_checkpoint.root - ): + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + store.justified_checkpoint = state.current_justified_checkpoint + return + + # Update justified if store justified is not in chain with finalized checkpoint + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot != store.finalized_checkpoint.root: store.justified_checkpoint = state.current_justified_checkpoint ``` From e46d5effe474e77b3cbc40b43121ee41893d17e1 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Wed, 10 Jun 2020 17:20:42 +0100 Subject: [PATCH 078/236] Add test for slashing after failing to respond to custody chunk challenge --- configs/mainnet.yaml | 2 + configs/minimal.yaml | 2 + specs/phase1/beacon-chain.md | 2 +- .../test_process_challenge_deadlines.py | 76 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 6ba8085a5e..2e15e84fdd 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -207,6 +207,8 @@ EPOCHS_PER_CUSTODY_PERIOD: 2048 CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 # 2**7 (= 128) epochs, ~14 hours MAX_REVEAL_LATENESS_DECREMENT: 128 +# 2**14 (= 16,384) epochs +CUSTODY_RESPONSE_DEADLINE: 16384 # Max operations # 2**8 (= 256) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 87509a4428..d99aa164ae 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -208,6 +208,8 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 EPOCHS_PER_CUSTODY_PERIOD: 8 # 2**11 (= 2,048) epochs CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 +# 2**14 (= 16,384) epochs +CUSTODY_RESPONSE_DEADLINE: 32 # Max operations # 2**8 (= 256) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 9d55cde3e7..e9ba4d88d1 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1137,7 +1137,7 @@ def process_phase_1_final_updates(state: BeaconState) -> None: #### Custody game updates -`process_reveal_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./custody-game.md), +`process_reveal_deadlines`, `process_challenge_deadlines` and `process_custody_final_updates` are defined in [the Custody Game spec](./custody-game.md), #### Online-tracking diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py new file mode 100644 index 0000000000..2e60d167cb --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py @@ -0,0 +1,76 @@ +from eth2spec.test.helpers.custody import ( + get_valid_chunk_challenge, + get_shard_transition, + get_valid_custody_key_reveal, +) +from eth2spec.test.helpers.attestations import ( + get_valid_on_time_attestation, +) +from eth2spec.test.helpers.state import next_epoch_via_block, transition_to +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with + +from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( + run_chunk_challenge_processing, +) +from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing + + +def run_process_challenge_deadlines(spec, state): + yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_validator_slashed_after_chunk_challenge(spec, state): + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + validator_index = spec.get_beacon_committee( + state, + attestation.data.slot, + attestation.data.index + )[0] + + spec.initiate_validator_exit(state, validator_index) + assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + + assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH + + while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: + next_epoch_via_block(spec, state) + while (state.validators[validator_index].next_custody_secret_to_reveal + <= spec.get_custody_period_for_validator( + validator_index, + state.validators[validator_index].exit_epoch - 1)): + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + next_epoch_via_block(spec, state) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + _, _, _ = run_chunk_challenge_processing(spec, state, challenge) + + assert state.validators[validator_index].slashed == 0 + + transition_to(spec, state, state.slot + (spec.CUSTODY_RESPONSE_DEADLINE + + spec.EPOCHS_PER_CUSTODY_PERIOD) * spec.SLOTS_PER_EPOCH) + + yield from run_process_challenge_deadlines(spec, state) + + assert state.validators[validator_index].slashed == 1 From 4b8f132957e6e6b5f197b0a9fafe8064fd32f072 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 10 Jun 2020 11:32:02 -0500 Subject: [PATCH 079/236] pr feedback --- specs/phase1/validator.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 935f34efb7..9c61989894 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -75,6 +75,7 @@ See constants from [Phase 0 validator guide](../phase0/validator.md#constants). | Name | Value | Unit | Duration | | - | - | :-: | :-: | | `TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT` | `2**3` (= 8) | validators | | +| `LIGHT_CLIENT_PREPARATION_EPOCHS` | `2**2` (= 4) | epochs | | ## Becoming a validator @@ -86,7 +87,7 @@ Beacon chain validator assignments to beacon committees and beacon block proposa ### Lookahead -Lookahead for beacon committee assignments operates in the same manner as Phase 0, but committee members must join a shard block pubsub topic in addition to the committee attestation topic.o +Lookahead for beacon committee assignments operates in the same manner as Phase 0, but committee members must join a shard block pubsub topic in addition to the committee attestation topic. Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a validator should: * Let `shard = compute_shard_from_committee_index(committe_index)` @@ -196,7 +197,8 @@ def get_best_light_client_aggregate(block: BeaconBlock, return max( viable_aggregates, - key=lambda a: len([i for i in a.aggregation_bits if i == 1]), + # Ties broken by lexicographically by hash_tree_root + key=lambda a: (len([i for i in a.aggregation_bits if i == 1]), hash_tree_root(a)), default=LightClientVote(), ) ``` @@ -378,9 +380,10 @@ When `get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == LIGHT_CLIENT_C If the validator is in the next light client committee, they must join the `light_client_votes` pubsub topic to begin duties at the start of the next period. ```python -def is_in_next_light_client_committee(state: BeaconState, index: ValidatorIndex) -> boolean: - period_start_epoch = get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD % get_current_epoch(state) - next_committee = get_light_client_committee(state, period_start_epoch) +def is_in_next_light_client_committee(state: BeaconState, index: ValidatorIndex) -> bool: + current_source_epoch = compute_committee_source_epoch(get_current_epoch(state), LIGHT_CLIENT_COMMITTEE_PERIOD) + next_source_epoch = current_source_epoch + LIGHT_CLIENT_COMMITTEE_PERIOD + next_committee = get_light_client_committee(state, next_source_epoch) return index in next_committee ``` From 6502cc1149921c9a27201e5021b341d00030b37e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 11 Jun 2020 00:40:09 +0800 Subject: [PATCH 080/236] Separate config files by phases --- configs/README.md | 2 +- configs/{mainnet.yaml => mainnet_phase0.yaml} | 67 ----------------- configs/mainnet_phase1.yaml | 69 ++++++++++++++++++ configs/{minimal.yaml => minimal_phase0.yaml} | 70 ------------------ configs/minimal_phase1.yaml | 71 +++++++++++++++++++ .../pyspec/eth2spec/config/config_util.py | 22 ++++-- 6 files changed, 157 insertions(+), 144 deletions(-) rename configs/{mainnet.yaml => mainnet_phase0.yaml} (69%) create mode 100644 configs/mainnet_phase1.yaml rename configs/{minimal.yaml => minimal_phase0.yaml} (69%) create mode 100644 configs/minimal_phase1.yaml diff --git a/configs/README.md b/configs/README.md index 4ca54e0142..be3c60e6f2 100644 --- a/configs/README.md +++ b/configs/README.md @@ -32,4 +32,4 @@ Each preset is a key-value mapping. Presets may contain comments to describe the values. -See [`mainnet.yaml`](./mainnet.yaml) for a complete example. +See [`mainnet_phase0.yaml`](./mainnet_phase0.yaml) for a complete example. diff --git a/configs/mainnet.yaml b/configs/mainnet_phase0.yaml similarity index 69% rename from configs/mainnet.yaml rename to configs/mainnet_phase0.yaml index 3631b7045f..39cfddf770 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet_phase0.yaml @@ -151,70 +151,3 @@ DOMAIN_DEPOSIT: 0x03000000 DOMAIN_VOLUNTARY_EXIT: 0x04000000 DOMAIN_SELECTION_PROOF: 0x05000000 DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 -# Phase 1 -DOMAIN_SHARD_PROPOSAL: 0x80000000 -DOMAIN_SHARD_COMMITTEE: 0x81000000 -DOMAIN_LIGHT_CLIENT: 0x82000000 -DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 - - -# Phase 1: Upgrade from Phase 0 -# --------------------------------------------------------------- -PHASE_1_FORK_VERSION: 0x01000000 -# [STUB] -PHASE_1_GENESIS_SLOT: 32 -INITIAL_ACTIVE_SHARDS: 64 - -# Phase 1: General -# --------------------------------------------------------------- -# 2**10` (= 1024) -MAX_SHARDS: 1024 -# 2**3 (= 8) | online epochs | ~51 min -ONLINE_PERIOD: 8 -# 2**7 (= 128) -LIGHT_CLIENT_COMMITTEE_SIZE: 128 -# 2**8 (= 256) | epochs | ~27 hours -LIGHT_CLIENT_COMMITTEE_PERIOD: 256 -# 2**18 (= 262,144) -SHARD_BLOCK_CHUNK_SIZE: 262144 -# 2**2 (= 4) -MAX_SHARD_BLOCK_CHUNKS: 4 -# 3 * 2**16` (= 196,608) -TARGET_SHARD_BLOCK_SIZE: 196608 -# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. -SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] -# len(SHARD_BLOCK_OFFSETS) -MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 -# 2**14 (= 16,384) Gwei -MAX_GASPRICE: 16384 -# 2**3 (= 8) Gwei -MIN_GASPRICE: 8 -# 2**3 (= 8) -GASPRICE_ADJUSTMENT_COEFFICIENT: 8 - - -# Phase 1: Custody Game -# --------------------------------------------------------------- - -# Time parameters -# 2**1 (= 2) epochs, 12.8 minutes -RANDAO_PENALTY_EPOCHS: 2 -# 2**14 (= 16,384) epochs ~73 days -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384 -# 2**11 (= 2,048) epochs, ~9 days -EPOCHS_PER_CUSTODY_PERIOD: 2048 -# 2**11 (= 2,048) epochs, ~9 days -CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 -# 2**7 (= 128) epochs, ~14 hours -MAX_REVEAL_LATENESS_DECREMENT: 128 - -# Max operations -# 2**8 (= 256) -MAX_CUSTODY_KEY_REVEALS: 256 -MAX_EARLY_DERIVED_SECRET_REVEALS: 1 -MAX_CUSTODY_SLASHINGS: 1 - -# Reward and penalty quotients -EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 -# 2**8 (= 256) -MINOR_REWARD_QUOTIENT: 256 diff --git a/configs/mainnet_phase1.yaml b/configs/mainnet_phase1.yaml new file mode 100644 index 0000000000..f85c538c8b --- /dev/null +++ b/configs/mainnet_phase1.yaml @@ -0,0 +1,69 @@ +# Mainnet preset - phase 1 + + +# Phase 1: Upgrade from Phase 0 +# --------------------------------------------------------------- +PHASE_1_FORK_VERSION: 0x01000000 +# [STUB] +PHASE_1_GENESIS_SLOT: 32 +INITIAL_ACTIVE_SHARDS: 64 + +# Phase 1: General +# --------------------------------------------------------------- +# Phase 1 domain +DOMAIN_SHARD_PROPOSAL: 0x80000000 +DOMAIN_SHARD_COMMITTEE: 0x81000000 +DOMAIN_LIGHT_CLIENT: 0x82000000 +DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 + +# 2**10` (= 1024) +MAX_SHARDS: 1024 +# 2**3 (= 8) | online epochs | ~51 min +ONLINE_PERIOD: 8 +# 2**7 (= 128) +LIGHT_CLIENT_COMMITTEE_SIZE: 128 +# 2**8 (= 256) | epochs | ~27 hours +LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# 2**18 (= 262,144) +SHARD_BLOCK_CHUNK_SIZE: 262144 +# 2**2 (= 4) +MAX_SHARD_BLOCK_CHUNKS: 4 +# 3 * 2**16` (= 196,608) +TARGET_SHARD_BLOCK_SIZE: 196608 +# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. +SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] +# len(SHARD_BLOCK_OFFSETS) +MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**14 (= 16,384) Gwei +MAX_GASPRICE: 16384 +# 2**3 (= 8) Gwei +MIN_GASPRICE: 8 +# 2**3 (= 8) +GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + + +# Phase 1: Custody Game +# --------------------------------------------------------------- + +# Time parameters +# 2**1 (= 2) epochs, 12.8 minutes +RANDAO_PENALTY_EPOCHS: 2 +# 2**14 (= 16,384) epochs ~73 days +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384 +# 2**11 (= 2,048) epochs, ~9 days +EPOCHS_PER_CUSTODY_PERIOD: 2048 +# 2**11 (= 2,048) epochs, ~9 days +CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 +# 2**7 (= 128) epochs, ~14 hours +MAX_REVEAL_LATENESS_DECREMENT: 128 + +# Max operations +# 2**8 (= 256) +MAX_CUSTODY_KEY_REVEALS: 256 +MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_SLASHINGS: 1 + +# Reward and penalty quotients +EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 +# 2**8 (= 256) +MINOR_REWARD_QUOTIENT: 256 diff --git a/configs/minimal.yaml b/configs/minimal_phase0.yaml similarity index 69% rename from configs/minimal.yaml rename to configs/minimal_phase0.yaml index 174694add3..524d0d5f9c 100644 --- a/configs/minimal.yaml +++ b/configs/minimal_phase0.yaml @@ -151,73 +151,3 @@ DOMAIN_DEPOSIT: 0x03000000 DOMAIN_VOLUNTARY_EXIT: 0x04000000 DOMAIN_SELECTION_PROOF: 0x05000000 DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 -# Phase 1 -DOMAIN_SHARD_PROPOSAL: 0x80000000 -DOMAIN_SHARD_COMMITTEE: 0x81000000 -DOMAIN_LIGHT_CLIENT: 0x82000000 -DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 - - -# Phase 1: Upgrade from Phase 0 -# --------------------------------------------------------------- -# [customized] for testnet distinction -PHASE_1_FORK_VERSION: 0x01000001 -# [customized] for testing -PHASE_1_GENESIS_SLOT: 8 -# [customized] reduced for testing -INITIAL_ACTIVE_SHARDS: 2 - - -# Phase 1: General -# --------------------------------------------------------------- -# [customized] reduced for testing -MAX_SHARDS: 8 -# 2**3 (= 8) | online epochs -ONLINE_PERIOD: 8 -# 2**7 (= 128) -LIGHT_CLIENT_COMMITTEE_SIZE: 128 -# 2**8 (= 256) | epochs -LIGHT_CLIENT_COMMITTEE_PERIOD: 256 -# 2**18 (= 262,144) -SHARD_BLOCK_CHUNK_SIZE: 262144 -# 2**2 (= 4) -MAX_SHARD_BLOCK_CHUNKS: 4 -# 3 * 2**16` (= 196,608) -TARGET_SHARD_BLOCK_SIZE: 196608 -# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. -SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] -# len(SHARD_BLOCK_OFFSETS) -MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 -# 2**14 (= 16,384) Gwei -MAX_GASPRICE: 16384 -# 2**3 (= 8) Gwei -MIN_GASPRICE: 8 -# 2**3 (= 8) -GASPRICE_ADJUSTMENT_COEFFICIENT: 8 - - -# Phase 1: Custody Game -# --------------------------------------------------------------- - -# Time parameters -# 2**1 (= 2) epochs -RANDAO_PENALTY_EPOCHS: 2 -# [customized] quicker for testing -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 -# 2**11 (= 2,048) epochs -EPOCHS_PER_CUSTODY_PERIOD: 2048 -# 2**11 (= 2,048) epochs -CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 -# 2**7 (= 128) epochs -MAX_REVEAL_LATENESS_DECREMENT: 128 - -# Max operations -# 2**8 (= 256) -MAX_CUSTODY_KEY_REVEALS: 256 -MAX_EARLY_DERIVED_SECRET_REVEALS: 1 -MAX_CUSTODY_SLASHINGS: 1 - -# Reward and penalty quotients -EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 -# 2**8 (= 256) -MINOR_REWARD_QUOTIENT: 256 diff --git a/configs/minimal_phase1.yaml b/configs/minimal_phase1.yaml new file mode 100644 index 0000000000..a7066ed92a --- /dev/null +++ b/configs/minimal_phase1.yaml @@ -0,0 +1,71 @@ +# Minimal preset - phase 1 + + +# Phase 1: Upgrade from Phase 0 +# --------------------------------------------------------------- +# [customized] for testnet distinction +PHASE_1_FORK_VERSION: 0x01000001 +# [customized] for testing +PHASE_1_GENESIS_SLOT: 8 +# [customized] reduced for testing +INITIAL_ACTIVE_SHARDS: 2 + +# Phase 1: General +# --------------------------------------------------------------- +# Phase 1 domain +DOMAIN_SHARD_PROPOSAL: 0x80000000 +DOMAIN_SHARD_COMMITTEE: 0x81000000 +DOMAIN_LIGHT_CLIENT: 0x82000000 +DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 + +# [customized] reduced for testing +MAX_SHARDS: 8 +# 2**3 (= 8) | online epochs +ONLINE_PERIOD: 8 +# 2**7 (= 128) +LIGHT_CLIENT_COMMITTEE_SIZE: 128 +# 2**8 (= 256) | epochs +LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# 2**18 (= 262,144) +SHARD_BLOCK_CHUNK_SIZE: 262144 +# 2**2 (= 4) +MAX_SHARD_BLOCK_CHUNKS: 4 +# 3 * 2**16` (= 196,608) +TARGET_SHARD_BLOCK_SIZE: 196608 +# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. +SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] +# len(SHARD_BLOCK_OFFSETS) +MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**14 (= 16,384) Gwei +MAX_GASPRICE: 16384 +# 2**3 (= 8) Gwei +MIN_GASPRICE: 8 +# 2**3 (= 8) +GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + + +# Phase 1: Custody Game +# --------------------------------------------------------------- + +# Time parameters +# 2**1 (= 2) epochs +RANDAO_PENALTY_EPOCHS: 2 +# [customized] quicker for testing +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 +# 2**11 (= 2,048) epochs +EPOCHS_PER_CUSTODY_PERIOD: 2048 +# 2**11 (= 2,048) epochs +CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 +# 2**7 (= 128) epochs +MAX_REVEAL_LATENESS_DECREMENT: 128 + +# Max operations +# 2**8 (= 256) +MAX_CUSTODY_KEY_REVEALS: 256 +MAX_EARLY_DERIVED_SECRET_REVEALS: 1 +MAX_CUSTODY_SLASHINGS: 1 + +# Reward and penalty quotients +EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 +# 2**8 (= 256) +MINOR_REWARD_QUOTIENT: 256 diff --git a/tests/core/pyspec/eth2spec/config/config_util.py b/tests/core/pyspec/eth2spec/config/config_util.py index c43c1521bc..9a6f3c6486 100644 --- a/tests/core/pyspec/eth2spec/config/config_util.py +++ b/tests/core/pyspec/eth2spec/config/config_util.py @@ -1,8 +1,10 @@ -from ruamel.yaml import YAML -from pathlib import Path +import os from os.path import join +from pathlib import Path from typing import Dict, Any +from ruamel.yaml import YAML + config: Dict[str, Any] = {} @@ -35,11 +37,19 @@ def load_config_file(configs_dir: str, presets_name: str) -> Dict[str, Any]: :param presets_name: The name of the presets. (lowercase snake_case) :return: Dictionary, mapping of constant-name -> constant-value """ - path = Path(join(configs_dir, presets_name + '.yaml')) - yaml = YAML(typ='base') - loaded = yaml.load(path) + _, _, config_files = next(os.walk(configs_dir)) + config_files.sort() + loaded_config = {} + for config_file_name in config_files: + if config_file_name.startswith(presets_name): + yaml = YAML(typ='base') + path = Path(join(configs_dir, config_file_name)) + loaded = yaml.load(path) + loaded_config.update(loaded) + assert loaded_config != {} + out: Dict[str, Any] = dict() - for k, v in loaded.items(): + for k, v in loaded_config.items(): if isinstance(v, list): # Clean up integer values. YAML parser renders lists of ints as list of str out[k] = [int(item) if item.isdigit() else item for item in v] From 85ec791935a046af228f4590eef9efbbb4095046 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 11 Jun 2020 00:58:40 +0800 Subject: [PATCH 081/236] Sync with beacon-chain spec --- configs/mainnet_phase1.yaml | 50 +++++++++++++++++++++---------------- configs/minimal_phase1.yaml | 49 ++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/configs/mainnet_phase1.yaml b/configs/mainnet_phase1.yaml index f85c538c8b..f9bd744b0d 100644 --- a/configs/mainnet_phase1.yaml +++ b/configs/mainnet_phase1.yaml @@ -1,45 +1,51 @@ # Mainnet preset - phase 1 -# Phase 1: Upgrade from Phase 0 +# # phase1-fork # --------------------------------------------------------------- PHASE_1_FORK_VERSION: 0x01000000 # [STUB] PHASE_1_GENESIS_SLOT: 32 INITIAL_ACTIVE_SHARDS: 64 -# Phase 1: General -# --------------------------------------------------------------- -# Phase 1 domain -DOMAIN_SHARD_PROPOSAL: 0x80000000 -DOMAIN_SHARD_COMMITTEE: 0x81000000 -DOMAIN_LIGHT_CLIENT: 0x82000000 -DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 -# 2**10` (= 1024) -MAX_SHARDS: 1024 -# 2**3 (= 8) | online epochs | ~51 min -ONLINE_PERIOD: 8 +# beacon-chain +# --------------------------------------------------------------- +# Misc +# 2**10 (= 1,024) +MAX_SHARDS: 8 # 2**7 (= 128) LIGHT_CLIENT_COMMITTEE_SIZE: 128 -# 2**8 (= 256) | epochs | ~27 hours -LIGHT_CLIENT_COMMITTEE_PERIOD: 256 -# 2**18 (= 262,144) -SHARD_BLOCK_CHUNK_SIZE: 262144 -# 2**2 (= 4) -MAX_SHARD_BLOCK_CHUNKS: 4 -# 3 * 2**16` (= 196,608) -TARGET_SHARD_BLOCK_SIZE: 196608 +# 2**3 (= 8) +GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + +# Shard block configs +# 2**20 (= 1048,576) bytes +MAX_SHARD_BLOCK_SIZE: 1048576 +# 2**18 (= 262,144) bytes +TARGET_SHARD_BLOCK_SIZE: 262144 # Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] # len(SHARD_BLOCK_OFFSETS) MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 + +# Gwei values # 2**14 (= 16,384) Gwei MAX_GASPRICE: 16384 # 2**3 (= 8) Gwei MIN_GASPRICE: 8 -# 2**3 (= 8) -GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + +# Time parameters +# 2**3 (= 8) | online epochs +ONLINE_PERIOD: 8 +# 2**8 (= 256) | epochs +LIGHT_CLIENT_COMMITTEE_PERIOD: 256 + +# Domain types +DOMAIN_SHARD_PROPOSAL: 0x80000000 +DOMAIN_SHARD_COMMITTEE: 0x81000000 +DOMAIN_LIGHT_CLIENT: 0x82000000 +DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 # Phase 1: Custody Game diff --git a/configs/minimal_phase1.yaml b/configs/minimal_phase1.yaml index a7066ed92a..556ff2618f 100644 --- a/configs/minimal_phase1.yaml +++ b/configs/minimal_phase1.yaml @@ -1,7 +1,7 @@ # Minimal preset - phase 1 -# Phase 1: Upgrade from Phase 0 +# phase1-fork # --------------------------------------------------------------- # [customized] for testnet distinction PHASE_1_FORK_VERSION: 0x01000001 @@ -10,43 +10,48 @@ PHASE_1_GENESIS_SLOT: 8 # [customized] reduced for testing INITIAL_ACTIVE_SHARDS: 2 -# Phase 1: General -# --------------------------------------------------------------- -# Phase 1 domain -DOMAIN_SHARD_PROPOSAL: 0x80000000 -DOMAIN_SHARD_COMMITTEE: 0x81000000 -DOMAIN_LIGHT_CLIENT: 0x82000000 -DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 +# beacon-chain +# --------------------------------------------------------------- +# Misc # [customized] reduced for testing MAX_SHARDS: 8 -# 2**3 (= 8) | online epochs -ONLINE_PERIOD: 8 # 2**7 (= 128) LIGHT_CLIENT_COMMITTEE_SIZE: 128 -# 2**8 (= 256) | epochs -LIGHT_CLIENT_COMMITTEE_PERIOD: 256 -# 2**18 (= 262,144) -SHARD_BLOCK_CHUNK_SIZE: 262144 -# 2**2 (= 4) -MAX_SHARD_BLOCK_CHUNKS: 4 -# 3 * 2**16` (= 196,608) -TARGET_SHARD_BLOCK_SIZE: 196608 +# 2**3 (= 8) +GASPRICE_ADJUSTMENT_COEFFICIENT: 8 + +# Shard block configs +# 2**20 (= 1048,576) bytes +MAX_SHARD_BLOCK_SIZE: 1048576 +# 2**18 (= 262,144) bytes +TARGET_SHARD_BLOCK_SIZE: 262144 # Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length. SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] # len(SHARD_BLOCK_OFFSETS) MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 + +# Gwei values # 2**14 (= 16,384) Gwei MAX_GASPRICE: 16384 # 2**3 (= 8) Gwei MIN_GASPRICE: 8 -# 2**3 (= 8) -GASPRICE_ADJUSTMENT_COEFFICIENT: 8 +# Time parameters +# 2**3 (= 8) | online epochs +ONLINE_PERIOD: 8 +# 2**8 (= 256) | epochs +LIGHT_CLIENT_COMMITTEE_PERIOD: 256 + +# Domain types +DOMAIN_SHARD_PROPOSAL: 0x80000000 +DOMAIN_SHARD_COMMITTEE: 0x81000000 +DOMAIN_LIGHT_CLIENT: 0x82000000 +DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 -# Phase 1: Custody Game -# --------------------------------------------------------------- +# custody-game +# --------------------------------------------------------------- # Time parameters # 2**1 (= 2) epochs RANDAO_PENALTY_EPOCHS: 2 From a1a75a38fe67c763f6d524ae6ce05fbb325186ad Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 11 Jun 2020 11:51:18 +1000 Subject: [PATCH 082/236] Tidy, add comment --- specs/phase0/fork-choice.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index b9d8ecd3c3..3cd45f1c1e 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -343,8 +343,9 @@ def on_tick(store: Store, time: uint64) -> None: def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: block = signed_block.message # Make a copy of the state to avoid mutability issues - assert block.parent_root in store.block_states pre_state = store.block_states[block.parent_root].copy() + # Parent block must be known + assert block.parent_root in store.block_states # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. assert get_current_slot(store) >= block.slot # Add new block to the store From b85afcab7a36b0d51ae9625192d6a0cd301d4868 Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Thu, 11 Jun 2020 07:28:26 -0700 Subject: [PATCH 083/236] Added fix in store_target_checkpoint_state --- specs/phase0/fork-choice.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index b9d8ecd3c3..614c21cc63 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -301,7 +301,8 @@ def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: # Store target checkpoint state if not yet seen if target not in store.checkpoint_states: base_state = store.block_states[target.root].copy() - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + if base_state.slot < compute_start_slot_at_epoch(target.epoch): + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) store.checkpoint_states[target] = base_state ``` From aa75fb0b693f3402c539ad0ad0f53be6c2de7869 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 12 Jun 2020 00:47:26 +0800 Subject: [PATCH 084/236] Fix `MAX_SHARDS` and move config files to `configs/{network}/{fork}` --- .../{mainnet_phase0.yaml => mainnet/phase0.yaml} | 0 .../{mainnet_phase1.yaml => mainnet/phase1.yaml} | 7 +++---- .../{minimal_phase0.yaml => minimal/phase0.yaml} | 0 .../{minimal_phase1.yaml => minimal/phase1.yaml} | 0 tests/core/pyspec/eth2spec/config/config_util.py | 13 ++++++------- 5 files changed, 9 insertions(+), 11 deletions(-) rename configs/{mainnet_phase0.yaml => mainnet/phase0.yaml} (100%) rename configs/{mainnet_phase1.yaml => mainnet/phase1.yaml} (97%) rename configs/{minimal_phase0.yaml => minimal/phase0.yaml} (100%) rename configs/{minimal_phase1.yaml => minimal/phase1.yaml} (100%) diff --git a/configs/mainnet_phase0.yaml b/configs/mainnet/phase0.yaml similarity index 100% rename from configs/mainnet_phase0.yaml rename to configs/mainnet/phase0.yaml diff --git a/configs/mainnet_phase1.yaml b/configs/mainnet/phase1.yaml similarity index 97% rename from configs/mainnet_phase1.yaml rename to configs/mainnet/phase1.yaml index f9bd744b0d..cddbb8dfe5 100644 --- a/configs/mainnet_phase1.yaml +++ b/configs/mainnet/phase1.yaml @@ -1,7 +1,7 @@ # Mainnet preset - phase 1 -# # phase1-fork +# phase1-fork # --------------------------------------------------------------- PHASE_1_FORK_VERSION: 0x01000000 # [STUB] @@ -13,7 +13,7 @@ INITIAL_ACTIVE_SHARDS: 64 # --------------------------------------------------------------- # Misc # 2**10 (= 1,024) -MAX_SHARDS: 8 +MAX_SHARDS: 1024 # 2**7 (= 128) LIGHT_CLIENT_COMMITTEE_SIZE: 128 # 2**3 (= 8) @@ -48,9 +48,8 @@ DOMAIN_LIGHT_CLIENT: 0x82000000 DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 -# Phase 1: Custody Game +# custody-game # --------------------------------------------------------------- - # Time parameters # 2**1 (= 2) epochs, 12.8 minutes RANDAO_PENALTY_EPOCHS: 2 diff --git a/configs/minimal_phase0.yaml b/configs/minimal/phase0.yaml similarity index 100% rename from configs/minimal_phase0.yaml rename to configs/minimal/phase0.yaml diff --git a/configs/minimal_phase1.yaml b/configs/minimal/phase1.yaml similarity index 100% rename from configs/minimal_phase1.yaml rename to configs/minimal/phase1.yaml diff --git a/tests/core/pyspec/eth2spec/config/config_util.py b/tests/core/pyspec/eth2spec/config/config_util.py index 9a6f3c6486..1977e5b054 100644 --- a/tests/core/pyspec/eth2spec/config/config_util.py +++ b/tests/core/pyspec/eth2spec/config/config_util.py @@ -1,5 +1,4 @@ import os -from os.path import join from pathlib import Path from typing import Dict, Any @@ -37,15 +36,15 @@ def load_config_file(configs_dir: str, presets_name: str) -> Dict[str, Any]: :param presets_name: The name of the presets. (lowercase snake_case) :return: Dictionary, mapping of constant-name -> constant-value """ - _, _, config_files = next(os.walk(configs_dir)) + present_dir = Path(configs_dir) / presets_name + _, _, config_files = next(os.walk(present_dir)) config_files.sort() loaded_config = {} for config_file_name in config_files: - if config_file_name.startswith(presets_name): - yaml = YAML(typ='base') - path = Path(join(configs_dir, config_file_name)) - loaded = yaml.load(path) - loaded_config.update(loaded) + yaml = YAML(typ='base') + path = present_dir / config_file_name + loaded = yaml.load(path) + loaded_config.update(loaded) assert loaded_config != {} out: Dict[str, Any] = dict() From bcfaa1b635954dc9cdf47d02a73aa1b5dbb38eb9 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 11:07:44 +0100 Subject: [PATCH 085/236] Fix tests --- Makefile | 2 +- .../test_process_chunk_challenge.py | 4 ++-- .../test_process_custody_key_reveal.py | 14 -------------- .../test_process_custody_slashing.py | 2 +- .../test_process_reveal_deadlines.py | 15 +++++++++------ 5 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 148daaa48d..419500ebbf 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --ignore=E252,W504,W503,E128 --max-line-length=120 ./eth2spec \ + flake8 --ignore=E252,W504,W503,E128,C901 --max-line-length=120 ./eth2spec \ && cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \ && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1; diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py index c71785518d..97fe0c222b 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -158,7 +158,7 @@ def test_multiple_epochs_custody(spec, state): @with_all_phases_except(['phase0']) @spec_state_test def test_many_epochs_custody(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) @@ -254,7 +254,7 @@ def test_custody_response_multiple_epochs(spec, state): @with_all_phases_except(['phase0']) @spec_state_test def test_custody_response_many_epochs(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py index fd16a344ca..cb96c97e1c 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py @@ -87,17 +87,3 @@ def test_double_reveal(spec, state): _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) - - -@with_all_phases_except([PHASE0]) -@spec_state_test -@always_bls -def test_max_decrement(spec, state): - state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150 - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - custody_key_reveal2 = get_valid_custody_key_reveal(spec, state) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal2) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py index 087e619f5b..997e5ac92b 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -118,7 +118,7 @@ def test_multiple_epochs_custody(spec, state): @with_all_phases_except(['phase0']) @spec_state_test def test_many_epochs_custody(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 100) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py index 688b046c8e..31d45aa414 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py @@ -1,7 +1,7 @@ from eth2spec.test.helpers.custody import ( get_valid_custody_key_reveal, ) -from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -19,9 +19,12 @@ def run_process_challenge_deadlines(spec, state): def test_validator_slashed_after_reveal_deadline(spec, state): assert state.validators[0].slashed == 0 - state.slot += ((spec.CHUNK_RESPONSE_DEADLINE + spec.EPOCHS_PER_CUSTODY_PERIOD) - * spec.SLOTS_PER_EPOCH) - next_epoch(spec, state) + transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH) + + transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) + * spec.SLOTS_PER_EPOCH)) + + state.validators[0].slashed = 0 yield from run_process_challenge_deadlines(spec, state) @@ -38,8 +41,8 @@ def test_validator_not_slashed_after_reveal(spec, state): assert state.validators[0].slashed == 0 - state.slot += spec.CHUNK_RESPONSE_DEADLINE * spec.SLOTS_PER_EPOCH - next_epoch(spec, state) + transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) + * spec.SLOTS_PER_EPOCH)) yield from run_process_challenge_deadlines(spec, state) From 7c6280aa8ef716262fc93a6fd4ba4bfb47a45636 Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:16:19 +0100 Subject: [PATCH 086/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index d05476af39..3875f28e52 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -264,8 +264,12 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) secrets = get_custody_secrets(key) custody_atoms = get_custody_atoms(data) n = len(custody_atoms) - uhf = (sum(secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME - for i, atom in enumerate(custody_atoms)) + secrets[n % CUSTODY_SECRETS]**n) % CUSTODY_PRIME + uhf = ( + sum( + secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME + for i, atom in enumerate(custody_atoms) + ) + secrets[n % CUSTODY_SECRETS]**n + ) % CUSTODY_PRIME return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) ``` From 31654d8bf459b0af62e1766129f9843a41d07149 Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:17:44 +0100 Subject: [PATCH 087/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 3875f28e52..84780846b2 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -252,7 +252,7 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: full_G2_element = bls.signature_to_G2(key) signature = full_G2_element[0].coeffs signature_bytes = b"".join(x.to_bytes(48, "little") for x in signature) - secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") + secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") for i in range(0, len(signature_bytes), 32)] return secrets ``` From d41b6a5775b42fb56588acb2cfda77c2ba34cafe Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:18:07 +0100 Subject: [PATCH 088/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 84780846b2..24d64b0ee0 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -380,8 +380,8 @@ def process_chunk_challenge_response(state: BeaconState, root=challenge.data_root, ) # Clear the challenge - records = state.custody_chunk_challenge_records - records[records.index(challenge)] = CustodyChunkChallengeRecord() + index_in_records = state.custody_chunk_challenge_records.index(challenge) + state.custody_chunk_challenge_records[index_in_records] = 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)) From 0e8bba2ce326ba15468d9d192fb727cac020d8db Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:28:30 +0100 Subject: [PATCH 089/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 24d64b0ee0..5ee38be8bf 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -519,8 +519,10 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed shard_transition = custody_slashing.shard_transition assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert custody_slashing.data.get_backing().get_left().merkle_root() \ + assert ( + custody_slashing.data.get_backing().get_left().merkle_root() == shard_transition.shard_data_roots[custody_slashing.data_index] + ) assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] # Verify existence and participation of claimed malefactor From 7bf491d49dff596f66a5eaea395f3f85bb22a26f Mon Sep 17 00:00:00 2001 From: dankrad Date: Fri, 12 Jun 2020 11:28:49 +0100 Subject: [PATCH 090/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 5ee38be8bf..d4ce4640ab 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -580,8 +580,8 @@ 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[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord() + index_in_records = records.index(custody_chunk_challenge) + state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() ``` ### Final updates From 65c3417f90229c136f797a4592db3d56c7841197 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 11:53:00 +0100 Subject: [PATCH 091/236] Fix replace_empty_or_append, remove assert False & test --- specs/phase1/custody-game.md | 13 +++++------ .../test_process_chunk_challenge.py | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index d4ce4640ab..8185b6baa6 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -193,14 +193,13 @@ class EarlyDerivedSecretReveal(Container): ### `replace_empty_or_append` ```python -def replace_empty_or_append(list: List, new_element: Any) -> int: - for i in range(len(list)): - if list[i] == type(new_element)(): - assert False - list[i] = new_element +def replace_empty_or_append(l: List, new_element: Any) -> int: + for i in range(len(l)): + if l[i] == type(new_element)(): + l[i] = new_element return i - list.append(new_element) - return len(list) - 1 + l.append(new_element) + return len(l) - 1 ``` ### `legendre_bit` diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py index 97fe0c222b..94b1c3b110 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -85,6 +85,29 @@ def test_challenge_appended(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) +@with_all_phases_except(['phase0']) +@spec_state_test +def test_challenge_empty_element_replaced(spec, state): + transition_to(spec, state, state.slot + 1) + shard = 0 + offset_slots = spec.get_offset_slots(state, shard) + shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, + shard_transition=shard_transition) + + transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) + + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) + + state.custody_chunk_challenge_records.append(spec.CustodyChunkChallengeRecord()) + + yield from run_chunk_challenge_processing(spec, state, challenge) + + @with_all_phases_except(['phase0']) @spec_state_test def test_duplicate_challenge(spec, state): From 59b35afcd92e87a4c7b309269fe68e993380521a Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 12:04:30 +0100 Subject: [PATCH 092/236] Refactor universal hash function --- specs/phase1/custody-game.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 8185b6baa6..1f3ad43787 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -256,19 +256,26 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: return secrets ``` -### `compute_custody_bit` +### `universal_hash_function` ```python -def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit: - secrets = get_custody_secrets(key) - custody_atoms = get_custody_atoms(data) - n = len(custody_atoms) - uhf = ( +def universal_hash_function(data_chunks: Sequence[bytes], secrets: Sequence[int]) -> int: + n = len(data_chunks) + return ( sum( secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME - for i, atom in enumerate(custody_atoms) + for i, atom in enumerate(data_chunks) ) + secrets[n % CUSTODY_SECRETS]**n ) % CUSTODY_PRIME +``` + +### `compute_custody_bit` + +```python +def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit: + custody_atoms = get_custody_atoms(data) + secrets = get_custody_secrets(key) + uhf = universal_hash_function(custody_atoms, secrets) return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) ``` @@ -579,7 +586,7 @@ 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) - index_in_records = records.index(custody_chunk_challenge) + index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge) state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() ``` From 398fc833b8151e6ac808b4a0d0d37459583c9c61 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 12:07:31 +0100 Subject: [PATCH 093/236] Fix TOC --- specs/phase1/custody-game.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 1f3ad43787..bab202cf1b 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -30,6 +30,7 @@ - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) - [`get_custody_secrets`](#get_custody_secrets) + - [`universal_hash_function`](#universal_hash_function) - [`compute_custody_bit`](#compute_custody_bit) - [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period) - [`get_custody_period_for_validator`](#get_custody_period_for_validator) From 04fb9926e819535d32d5c22ed8616c4a0b8a25e9 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 17:16:08 +0100 Subject: [PATCH 094/236] Remove custody bits from phase 1 and tests --- specs/phase1/beacon-chain.md | 131 +----------------- specs/phase1/custody-game.md | 18 +-- specs/phase1/fork-choice.md | 34 ----- .../test/fork_choice/test_on_attestation.py | 7 +- .../eth2spec/test/helpers/attestations.py | 82 +---------- .../test/helpers/attester_slashings.py | 24 +--- .../test_process_attester_slashing.py | 7 +- .../test_process_attestation.py | 17 +-- 8 files changed, 27 insertions(+), 293 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 61e30e1022..04c28f5ab3 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -52,15 +52,12 @@ - [`get_shard_committee`](#get_shard_committee) - [`get_light_client_committee`](#get_light_client_committee) - [`get_shard_proposer_index`](#get_shard_proposer_index) - - [`get_indexed_attestation`](#get_indexed_attestation) - [`get_committee_count_delta`](#get_committee_count_delta) - [`get_start_shard`](#get_start_shard) - [`get_shard`](#get_shard) - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) - - [`verify_attestation_custody`](#verify_attestation_custody) - - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - [`is_on_time_attestation`](#is_on_time_attestation) - [`is_winning_attestation`](#is_winning_attestation) - [`optional_aggregate_verify`](#optional_aggregate_verify) @@ -77,7 +74,6 @@ - [`verify_empty_shard_transition`](#verify_empty_shard_transition) - [`process_shard_transitions`](#process_shard_transitions) - [New default validator for deposits](#new-default-validator-for-deposits) - - [New Attester slashing processing](#new-attester-slashing-processing) - [Light client processing](#light-client-processing) - [Epoch transition](#epoch-transition) - [Phase 1 final updates](#phase-1-final-updates) @@ -192,7 +188,6 @@ class AttestationData(Container): class Attestation(Container): aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] data: AttestationData - custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION] signature: BLSSignature ``` @@ -212,8 +207,9 @@ class PendingAttestation(Container): ```python class IndexedAttestation(Container): - committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] - attestation: Attestation + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE] + data: AttestationData + signature: BLSSignature ``` ### Extended `AttesterSlashing` @@ -599,17 +595,6 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard return committee[r % len(committee)] ``` -#### `get_indexed_attestation` - -```python -def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) -> IndexedAttestation: - committee = get_beacon_committee(beacon_state, attestation.data.slot, attestation.data.index) - return IndexedAttestation( - committee=committee, - attestation=attestation, - ) -``` - #### `get_committee_count_delta` ```python @@ -679,65 +664,6 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: ### Predicates -#### `verify_attestation_custody` - -```python -def verify_attestation_custody(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` has valid signature against non-empty custody bits. - """ - attestation = indexed_attestation.attestation - aggregation_bits = attestation.aggregation_bits - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) - all_pubkeys = [] - all_signing_roots = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - assert len(custody_bits) == len(indexed_attestation.committee) - for participant, aggregation_bit, custody_bit in zip( - indexed_attestation.committee, aggregation_bits, custody_bits - ): - if aggregation_bit: - all_pubkeys.append(state.validators[participant].pubkey) - # Note: only 2N distinct message hashes - attestation_wrapper = AttestationCustodyBitWrapper( - attestation_data_root=hash_tree_root(attestation.data), - block_index=block_index, - bit=custody_bit, - ) - all_signing_roots.append(compute_signing_root(attestation_wrapper, domain)) - else: - assert not custody_bit - return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature) -``` - -#### Updated `is_valid_indexed_attestation` - -Note that this replaces the Phase 0 `is_valid_indexed_attestation`. - -```python -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: - """ - Check if ``indexed_attestation`` has valid indices and signature. - """ - # Verify aggregate signature - attestation = indexed_attestation.attestation - aggregation_bits = attestation.aggregation_bits - if not any(aggregation_bits) or len(aggregation_bits) != len(indexed_attestation.committee): - return False - - if len(attestation.custody_bits_blocks) == 0: - # fall back on phase0 behavior if there is no shard data. - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) - all_pubkeys = [] - for participant, aggregation_bit in zip(indexed_attestation.committee, aggregation_bits): - if aggregation_bit: - all_pubkeys.append(state.validators[participant].pubkey) - signing_root = compute_signing_root(indexed_attestation.attestation.data, domain) - return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature) - else: - return verify_attestation_custody(state, indexed_attestation) -``` - #### `is_on_time_attestation` ```python @@ -855,16 +781,11 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint - # Type 1: on-time attestations, the custody bits should be non-empty. - if attestation.custody_bits_blocks != []: - # Ensure on-time attestation - assert is_on_time_attestation(state, attestation) - # Correct data root count - shard = get_shard(state, attestation) - assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) + # Type 1: on-time attestations + if is_on_time_attestation(state, attestation): # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) - # Type 2: no shard transition, no custody bits + # Type 2: no shard transition else: # Ensure delayed attestation assert data.slot < compute_previous_slot(state.slot) @@ -1087,46 +1008,6 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato ) ``` -##### New Attester slashing processing - -```python -def get_indices_from_committee( - committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE], - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Sequence[ValidatorIndex]: - assert len(bits) == len(committee) - return [validator_index for i, validator_index in enumerate(committee) if bits[i]] -``` - -```python -def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSlashing) -> None: - indexed_attestation_1 = attester_slashing.attestation_1 - indexed_attestation_2 = attester_slashing.attestation_2 - - assert is_slashable_attestation_data( - indexed_attestation_1.attestation.data, - indexed_attestation_2.attestation.data, - ) - assert is_valid_indexed_attestation(state, indexed_attestation_1) - assert is_valid_indexed_attestation(state, indexed_attestation_2) - - indices_1 = get_indices_from_committee( - indexed_attestation_1.committee, - indexed_attestation_1.attestation.aggregation_bits, - ) - indices_2 = get_indices_from_committee( - indexed_attestation_2.committee, - indexed_attestation_2.attestation.aggregation_bits, - ) - - slashed_any = False - indices = set(indices_1).intersection(indices_2) - for index in sorted(indices): - if is_slashable_validator(state.validators[index], get_current_epoch(state)): - slash_validator(state, index) - slashed_any = True - assert slashed_any -``` - #### Light client processing ```python diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index bab202cf1b..ca0e9c5084 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -60,6 +60,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `CUSTODY_PRIME` | `2 ** 256 - 189` | - | | `CUSTODY_SECRETS` | `3` | - | | `BYTES_PER_CUSTODY_ATOM` | `32` | bytes | +| `CUSTODY_PROBABILITY_EXPONENT` | `10` | - | ## Configuration @@ -141,7 +142,6 @@ class CustodyChunkResponse(Container): ```python class CustodySlashing(Container): - # Attestation.custody_bits_blocks[data_index][committee.index(malefactor_index)] is the target custody bit to check. # (Attestation.data.shard_transition_root as ShardTransition).shard_data_roots[data_index] is the root of the data. data_index: uint64 malefactor_index: ValidatorIndex @@ -277,7 +277,8 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) custody_atoms = get_custody_atoms(data) secrets = get_custody_secrets(key) uhf = universal_hash_function(custody_atoms, secrets) - return legendre_bit(uhf + secrets[0], CUSTODY_PRIME) + legendre_bits = [legendre_bit(uhf + secrets[0], CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] + return all(legendre_bits) ``` ### `get_randao_epoch_for_custody_period` @@ -518,9 +519,6 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed # Verify the attestation assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) - # TODO: custody_slashing.data is not chunked like shard blocks yet, result is lots of padding. - # ??? What does this mean? - # TODO: can do a single combined merkle proof of data being attested. # Verify the shard transition is indeed attested by the attestation shard_transition = custody_slashing.shard_transition @@ -545,18 +543,14 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed signing_root = compute_signing_root(epoch_to_sign, domain) assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret) - # Get the custody bit - custody_bits = attestation.custody_bits_blocks[custody_slashing.data_index] - committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) - claimed_custody_bit = custody_bits[committee.index(custody_slashing.malefactor_index)] - # Compute the custody bit computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data) - + # Verify the claim - if claimed_custody_bit != computed_custody_bit: + if computed_custody_bit == 1: # Slash the malefactor, reward the other committee members slash_validator(state, custody_slashing.malefactor_index) + committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) others_count = len(committee) - 1 whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count) for attester_index in attesters: diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md index 3f9fbdbfba..f92640ebb3 100644 --- a/specs/phase1/fork-choice.md +++ b/specs/phase1/fork-choice.md @@ -9,11 +9,9 @@ - [Introduction](#introduction) -- [Fork choice](#fork-choice) - [Helpers](#helpers) - [Extended `LatestMessage`](#extended-latestmessage) - [Updated `update_latest_messages`](#updated-update_latest_messages) - - [Handlers](#handlers) @@ -22,12 +20,6 @@ This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1. -## Fork choice - -Due to the changes in the structure of `IndexedAttestation` in Phase 1, `on_attestation` must be re-specified to handle this. The bulk of `on_attestation` has been moved out into a few helpers to reduce code duplication where possible. - -The rest of the fork choice remains stable. - ### Helpers #### Extended `LatestMessage` @@ -54,29 +46,3 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root ) ``` - -### Handlers - -```python -def on_attestation(store: Store, attestation: Attestation) -> None: - """ - Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. - - An ``attestation`` that is asserted as invalid may be valid at a later time, - consider scheduling it for later processing in such case. - """ - validate_on_attestation(store, attestation) - store_target_checkpoint_state(store, attestation.data.target) - - # Get state at the `target` to fully validate attestation - target_state = store.checkpoint_states[attestation.data.target] - indexed_attestation = get_indexed_attestation(target_state, attestation) - assert is_valid_indexed_attestation(target_state, indexed_attestation) - - # Update latest messages for attesting indices - attesting_indices = [ - index for i, index in enumerate(indexed_attestation.committee) - if attestation.aggregation_bits[i] - ] - update_latest_messages(store, attesting_indices, attestation) -``` diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index a5334c5c78..2c50544d84 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -16,18 +16,13 @@ def run_on_attestation(spec, state, store, attestation, valid=True): indexed_attestation = spec.get_indexed_attestation(state, attestation) spec.on_attestation(store, attestation) + sample_index = indexed_attestation.attesting_indices[0] if spec.fork == PHASE0: - sample_index = indexed_attestation.attesting_indices[0] latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, ) else: - attesting_indices = [ - index for i, index in enumerate(indexed_attestation.committee) - if attestation.aggregation_bits[i] - ] - sample_index = attesting_indices[0] latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 17ef717bc3..b9c91eaa05 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -2,14 +2,13 @@ from typing import List -from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 +from eth2spec.test.context import expect_assertion_error, PHASE1 from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.test.helpers.custody import get_custody_test_vector def run_attestation_processing(spec, state, attestation, valid=True): @@ -98,35 +97,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transition, - signed=False, valid_custody_bits=None): - shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - - if valid_custody_bits is not None: - beacon_committee = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index, - ) - custody_secrets = [None for i in beacon_committee] - for i in range(len(beacon_committee)): - period = spec.get_custody_period_for_validator(beacon_committee[i], attestation.data.target.epoch) - epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, beacon_committee[i]) - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) - signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) - custody_secrets[i] = bls.Sign(privkeys[beacon_committee[i]], signing_root) - - for i in range(len(offset_slots)): - attestation.custody_bits_blocks.append( - Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) - ) - if valid_custody_bits is not None: - test_vector = get_custody_test_vector(shard_transition.shard_block_lengths[i]) - for j in range(len(attestation.custody_bits_blocks[i])): - if attestation.aggregation_bits[j]: - attestation.custody_bits_blocks[i][j] = \ - spec.compute_custody_bit(custody_secrets[j], test_vector) ^ (not valid_custody_bits) - + signed=False): if signed: sign_attestation(spec, state, attestation) @@ -134,7 +105,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transit def get_valid_on_time_attestation(spec, state, slot=None, index=None, - shard_transition=None, valid_custody_bits=None, signed=False): + shard_transition=None, signed=False): ''' Construct on-time attestation for next slot ''' @@ -149,7 +120,6 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, slot=slot, index=index, shard_transition=shard_transition, - valid_custody_bits=valid_custody_bits, signed=signed, on_time=True, ) @@ -174,7 +144,6 @@ def get_valid_attestation(spec, index=None, filter_participant_set=None, shard_transition=None, - valid_custody_bits=None, signed=False, on_time=True): # If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed. @@ -207,7 +176,6 @@ def get_valid_attestation(spec, attestation = convert_to_valid_on_time_attestation( spec, state, attestation, shard_transition, - valid_custody_bits=valid_custody_bits, signed=signed, ) @@ -230,43 +198,9 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List def sign_indexed_attestation(spec, state, indexed_attestation): - if spec.fork == PHASE0: - participants = indexed_attestation.attesting_indices - data = indexed_attestation.data - indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants) - else: - participants = spec.get_indices_from_committee( - indexed_attestation.committee, - indexed_attestation.attestation.aggregation_bits, - ) - data = indexed_attestation.attestation.data - if any(indexed_attestation.attestation.custody_bits_blocks): - sign_on_time_attestation(spec, state, indexed_attestation.attestation) - else: - indexed_attestation.attestation.signature = sign_aggregate_attestation(spec, state, data, participants) - - -def sign_on_time_attestation(spec, state, attestation): - if not any(attestation.custody_bits_blocks): - sign_attestation(spec, state, attestation) - return - - committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index) - signatures = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits): - if not abit: - continue - signatures.append(get_attestation_custody_signature( - spec, - state, - attestation.data, - block_index, - cbit, - privkeys[participant] - )) - - attestation.signature = bls.Aggregate(signatures) + participants = indexed_attestation.attesting_indices + data = indexed_attestation.data + indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants) def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey): @@ -283,10 +217,6 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index def sign_attestation(spec, state, attestation): - if spec.fork == PHASE1 and any(attestation.custody_bits_blocks): - sign_on_time_attestation(spec, state, attestation) - return - participants = spec.get_attesting_indices( state, attestation.data, diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py index e743ca8ff6..43763895fc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py @@ -1,4 +1,3 @@ -from eth2spec.test.context import PHASE1 from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation @@ -41,34 +40,19 @@ def get_indexed_attestation_participants(spec, indexed_att): """ Wrapper around index-attestation to return the list of participant indices, regardless of spec phase. """ - if spec.fork == PHASE1: - return list(spec.get_indices_from_committee( - indexed_att.committee, - indexed_att.attestation.aggregation_bits, - )) - else: - return list(indexed_att.attesting_indices) + return list(indexed_att.attesting_indices) def set_indexed_attestation_participants(spec, indexed_att, participants): """ Wrapper around index-attestation to return the list of participant indices, regardless of spec phase. """ - if spec.fork == PHASE1: - indexed_att.attestation.aggregation_bits = [bool(i in participants) for i in indexed_att.committee] - else: - indexed_att.attesting_indices = participants + indexed_att.attesting_indices = participants def get_attestation_1_data(spec, att_slashing): - if spec.fork == PHASE1: - return att_slashing.attestation_1.attestation.data - else: - return att_slashing.attestation_1.data + return att_slashing.attestation_1.data def get_attestation_2_data(spec, att_slashing): - if spec.fork == PHASE1: - return att_slashing.attestation_2.attestation.data - else: - return att_slashing.attestation_2.data + return att_slashing.attestation_2.data diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py index 11ead60337..0635144988 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py @@ -1,5 +1,5 @@ from eth2spec.test.context import ( - PHASE0, PHASE1, + PHASE0, spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases ) from eth2spec.test.helpers.attestations import sign_indexed_attestation @@ -162,10 +162,7 @@ def test_same_data(spec, state): indexed_att_1 = attester_slashing.attestation_1 att_2_data = get_attestation_2_data(spec, attester_slashing) - if spec.fork == PHASE1: - indexed_att_1.attestation.data = att_2_data - else: - indexed_att_1.data = att_2_data + indexed_att_1.data = att_2_data sign_indexed_attestation(spec, state, attester_slashing.attestation_1) yield from run_attester_slashing_processing(spec, state, attester_slashing, False) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py index ed43283274..34ff284129 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py @@ -25,22 +25,9 @@ def test_on_time_success(spec, state): @with_all_phases_except(['phase0']) @spec_state_test @always_bls -def test_on_time_empty_custody_bits_blocks(spec, state): +def test_late_success(spec, state): attestation = get_valid_late_attestation(spec, state, signed=True) - assert not any(attestation.custody_bits_blocks) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - yield from run_attestation_processing(spec, state, attestation, False) - - -@with_all_phases_except(['phase0']) -@spec_state_test -@always_bls -def test_late_with_custody_bits_blocks(spec, state): - attestation = get_valid_on_time_attestation(spec, state, signed=True) - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + 1) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation) From 993ed5c2c6474af552326582153bebc2f210080d Mon Sep 17 00:00:00 2001 From: Aditya Asgaonkar Date: Fri, 12 Jun 2020 14:37:07 -0700 Subject: [PATCH 095/236] Resetting branch to dev and adding single commit --- specs/phase0/fork-choice.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 132477ee8a..02143d0a45 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -301,7 +301,8 @@ def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: # Store target checkpoint state if not yet seen if target not in store.checkpoint_states: base_state = store.block_states[target.root].copy() - process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) + if base_state.slot < compute_start_slot_at_epoch(target.epoch): + process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) store.checkpoint_states[target] = base_state ``` From f857dbfac246b29bdc92bc80c2ab861f4de114bd Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 22:47:45 +0100 Subject: [PATCH 096/236] Custody tests --- .../pyspec/eth2spec/test/helpers/custody.py | 46 +++++--- .../test_process_custody_slashing.py | 103 ++++++++++++++---- 2 files changed, 111 insertions(+), 38 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 898cf7731a..edd7ac19fe 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -59,7 +59,7 @@ def bitlist_from_int(max_len, num_bits, n): return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)]) -def get_valid_custody_slashing(spec, state, attestation, shard_transition, invalid_custody_bit=False): +def get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, data, data_index=0): beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, @@ -68,21 +68,10 @@ def get_valid_custody_slashing(spec, state, attestation, shard_transition, inval malefactor_index = beacon_committee[0] whistleblower_index = beacon_committee[-1] - epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch, - malefactor_index) - - # Generate the responder key - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch) - signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) - malefactor_key = bls.Sign(privkeys[malefactor_index], signing_root) - data_index = 0 - data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]( - get_custody_test_vector(shard_transition.shard_block_lengths[data_index])) - slashing = spec.CustodySlashing( data_index=data_index, malefactor_index=malefactor_index, - malefactor_secret=malefactor_key, + malefactor_secret=custody_secret, whistleblower_index=whistleblower_index, shard_transition=shard_transition, attestation=attestation, @@ -165,9 +154,9 @@ def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, ) -def get_custody_test_vector(bytelength): +def get_custody_test_vector(bytelength, offset=0): ints = bytelength // 4 + 1 - return (b"".join(i.to_bytes(4, "little") for i in range(ints)))[:bytelength] + return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength] def get_shard_transition(spec, start_slot, block_lengths): @@ -181,3 +170,30 @@ def get_shard_transition(spec, start_slot, block_lengths): proposer_signature_aggregate=spec.BLSSignature(), ) return shard_transition + + +def get_custody_secret(spec, state, validator_index, epoch=None): + period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None + else spec.get_current_epoch(state)) + epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index) + domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) + signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) + return bls.Sign(privkeys[validator_index], signing_root) + + +def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True): + test_vector = get_custody_test_vector(length) + offset = 0 + while spec.compute_custody_bit(custody_secret, test_vector) != slashable: + offset += 1 + test_vector = test_vector = get_custody_test_vector(length, offset) + return test_vector + + +def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True): + shard_transition = get_shard_transition(spec, start_slot, block_lengths) + slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, + block_lengths[0], slashable=slashable) + shard_transition.shard_data_roots[0] = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) \ + .get_backing().get_left().merkle_root() + return shard_transition, slashable_test_vector diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py index 997e5ac92b..c40aa17879 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -1,6 +1,7 @@ from eth2spec.test.helpers.custody import ( get_valid_custody_slashing, - get_shard_transition, + get_custody_secret, + get_custody_slashable_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -58,9 +59,18 @@ def test_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -68,8 +78,8 @@ def test_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -79,9 +89,18 @@ def test_incorrect_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret, slashable=False) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=True) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -89,7 +108,8 @@ def test_incorrect_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + slashing = get_valid_custody_slashing(spec, state, attestation, + shard_transition, custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=False) @@ -100,9 +120,18 @@ def test_multiple_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -110,8 +139,8 @@ def test_multiple_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -121,9 +150,18 @@ def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -131,8 +169,8 @@ def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -142,14 +180,23 @@ def test_off_chain_attestation(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) - + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) yield from run_custody_slashing_processing(spec, state, slashing, correct=True) @@ -159,9 +206,18 @@ def test_invalid_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + + validator_index = spec.get_beacon_committee( + state, + state.slot, + shard, + )[0] + custody_secret = get_custody_secret(spec, state, validator_index) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, + [2**15 // 3] * len(offset_slots), custody_secret) + attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition, valid_custody_bits=False) + shard_transition=shard_transition) transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -169,7 +225,8 @@ def test_invalid_custody_slashing(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition) + slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, + custody_secret, slashable_test_vector) slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() From 42a9f1afdf12faaac235e775d5db7e86720aa1c9 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Fri, 12 Jun 2020 23:44:36 +0100 Subject: [PATCH 097/236] Fix Legendre bit computations --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index ca0e9c5084..5cf5da7430 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -277,7 +277,7 @@ def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) custody_atoms = get_custody_atoms(data) secrets = get_custody_secrets(key) uhf = universal_hash_function(custody_atoms, secrets) - legendre_bits = [legendre_bit(uhf + secrets[0], CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] + legendre_bits = [legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] return all(legendre_bits) ``` From 7ad1bb508d6f339b8def65e2dac85eac3614ca89 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 13 Jun 2020 16:04:16 +1000 Subject: [PATCH 098/236] Ensure parent is checked before store lookup --- specs/phase0/fork-choice.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 3cd45f1c1e..d9cf873305 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -342,10 +342,10 @@ def on_tick(store: Store, time: uint64) -> None: ```python def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: block = signed_block.message - # Make a copy of the state to avoid mutability issues - pre_state = store.block_states[block.parent_root].copy() # Parent block must be known assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + pre_state = store.block_states[block.parent_root].copy() # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. assert get_current_slot(store) >= block.slot # Add new block to the store From f6d7dac30c6d29c3b6252daf6255daf7c217e046 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Sat, 13 Jun 2020 15:15:37 +0100 Subject: [PATCH 099/236] Change to 2**14 epoch (73 day) custody periods as per #1888 --- configs/mainnet.yaml | 17 ++++++------ configs/minimal.yaml | 19 +++++++------ specs/phase1/custody-game.md | 11 ++++---- .../test_process_custody_slashing.py | 2 +- .../test_process_challenge_deadlines.py | 27 +++---------------- .../test_process_reveal_deadlines.py | 13 ++++----- 6 files changed, 37 insertions(+), 52 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 2e15e84fdd..d2ab3cab67 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -196,19 +196,20 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8 # Phase 1: Custody Game # --------------------------------------------------------------- +# 1/1024 chance of custody bit 1 +CUSTODY_PROBABILITY_EXPONENT: 10 + # Time parameters # 2**1 (= 2) epochs, 12.8 minutes RANDAO_PENALTY_EPOCHS: 2 -# 2**14 (= 16,384) epochs ~73 days -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384 -# 2**11 (= 2,048) epochs, ~9 days -EPOCHS_PER_CUSTODY_PERIOD: 2048 +# 2**15 (= 32,768) epochs, ~146 days +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768 +# 2**14 (= 16,384) epochs ~73 days +EPOCHS_PER_CUSTODY_PERIOD: 16384 # 2**11 (= 2,048) epochs, ~9 days CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 -# 2**7 (= 128) epochs, ~14 hours -MAX_REVEAL_LATENESS_DECREMENT: 128 -# 2**14 (= 16,384) epochs -CUSTODY_RESPONSE_DEADLINE: 16384 +# 2**15 (= 32,768) epochs, ~146 days +MAX_CHUNK_CHALLENGE_DELAY: 32768 # Max operations # 2**8 (= 256) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index d99aa164ae..3fe160bbe2 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -199,17 +199,20 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8 # Phase 1: Custody Game # --------------------------------------------------------------- +# 1/1024 chance of custody bit 1 [customized for faster testing] +CUSTODY_PROBABILITY_EXPONENT: 2 + # Time parameters # 2**1 (= 2) epochs RANDAO_PENALTY_EPOCHS: 2 -# [customized] quicker for testing -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 -# 2**11 (= 2,048) epochs -EPOCHS_PER_CUSTODY_PERIOD: 8 -# 2**11 (= 2,048) epochs -CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# 2**14 (= 16,384) epochs -CUSTODY_RESPONSE_DEADLINE: 32 +# 2**15 (= 32,768) epochs, ~146 days [customized for faster testing] +EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128 +# 2**14 (= 16,384) epochs ~73 days [customized for faster testing] +EPOCHS_PER_CUSTODY_PERIOD: 64 +# 2**11 (= 2,048) epochs, ~9 days [customized for faster testing] +CUSTODY_PERIOD_TO_RANDAO_PADDING: 16 +# 2**15 (= 32,768) epochs, ~146 days +MAX_CHUNK_CHALLENGE_DELAY: 128 # Max operations # 2**8 (= 256) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 5cf5da7430..30119f3e36 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -69,12 +69,11 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | Unit | Duration | | - | - | :-: | :-: | | `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes | -| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**14` (= 16,384) | epochs | ~73 days | -| `EPOCHS_PER_CUSTODY_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days | +| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**15` (= 32,768) | epochs | ~146 days | +| `EPOCHS_PER_CUSTODY_PERIOD` | `2**14` (= 16,384) | epochs | ~73 days | | `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days | | `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | -| `MAX_CHUNK_CHALLENGE_DELAY` | `2**11` (= 2,048) | epochs | ~9 days | -| `CUSTODY_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `2**15` (= 32,768) | epochs | ~146 days | ### Max operations per block @@ -571,7 +570,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed def process_reveal_deadlines(state: BeaconState) -> None: epoch = get_current_epoch(state) for index, validator in enumerate(state.validators): - deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD) + deadline = validator.next_custody_secret_to_reveal + 1 if get_custody_period_for_validator(ValidatorIndex(index), epoch) > deadline: slash_validator(state, ValidatorIndex(index)) ``` @@ -579,7 +578,7 @@ def process_reveal_deadlines(state: BeaconState) -> None: ```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: + if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + EPOCHS_PER_CUSTODY_PERIOD: slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index) index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge) state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py index c40aa17879..d1649484e3 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -147,7 +147,7 @@ def test_multiple_epochs_custody(spec, state): @with_all_phases_except(['phase0']) @spec_state_test def test_many_epochs_custody(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 10) shard = 0 offset_slots = spec.get_offset_slots(state, shard) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py index 2e60d167cb..6590d8af90 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py @@ -1,12 +1,11 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_shard_transition, - get_valid_custody_key_reveal, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, ) -from eth2spec.test.helpers.state import next_epoch_via_block, transition_to +from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( with_all_phases_except, spec_state_test, @@ -17,7 +16,6 @@ from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, ) -from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing def run_process_challenge_deadlines(spec, state): @@ -44,32 +42,15 @@ def test_validator_slashed_after_chunk_challenge(spec, state): attestation.data.index )[0] - spec.initiate_validator_exit(state, validator_index) - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: - next_epoch_via_block(spec, state) - while (state.validators[validator_index].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator( - validator_index, - state.validators[validator_index].exit_epoch - 1)): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - next_epoch_via_block(spec, state) - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) _, _, _ = run_chunk_challenge_processing(spec, state, challenge) assert state.validators[validator_index].slashed == 0 - transition_to(spec, state, state.slot + (spec.CUSTODY_RESPONSE_DEADLINE + - spec.EPOCHS_PER_CUSTODY_PERIOD) * spec.SLOTS_PER_EPOCH) + transition_to(spec, state, state.slot + spec.MAX_CHUNK_CHALLENGE_DELAY * spec.SLOTS_PER_EPOCH) + + state.validators[validator_index].slashed = 0 yield from run_process_challenge_deadlines(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py index 31d45aa414..9cc0069b93 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py @@ -18,11 +18,13 @@ def run_process_challenge_deadlines(spec, state): @spec_state_test def test_validator_slashed_after_reveal_deadline(spec, state): assert state.validators[0].slashed == 0 - transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH) - transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) - * spec.SLOTS_PER_EPOCH)) + # Need to run at least one reveal so that not all validators are slashed (otherwise spec fails to find proposers) + custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=1) + _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) + + transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) state.validators[0].slashed = 0 @@ -34,15 +36,14 @@ def test_validator_slashed_after_reveal_deadline(spec, state): @with_all_phases_except(['phase0']) @spec_state_test def test_validator_not_slashed_after_reveal(spec, state): - state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH + transition_to(spec, state, spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) custody_key_reveal = get_valid_custody_key_reveal(spec, state) _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) assert state.validators[0].slashed == 0 - transition_to(spec, state, state.slot + ((spec.CUSTODY_RESPONSE_DEADLINE) - * spec.SLOTS_PER_EPOCH)) + transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) yield from run_process_challenge_deadlines(spec, state) From fdb6f158676585d5b10944b62f8eb8bc4daa7784 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sat, 13 Jun 2020 15:59:04 -0500 Subject: [PATCH 100/236] unhandled exceptions do not modify forkchoice store --- specs/phase0/fork-choice.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 95142f80ce..ecbabc6f66 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -44,9 +44,11 @@ This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0 The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_forkchoice_store(genesis_state)` and update `store` by running: -- `on_tick(time)` whenever `time > store.time` where `time` is the current Unix time -- `on_block(block)` whenever a block `block: SignedBeaconBlock` is received -- `on_attestation(attestation)` whenever an attestation `attestation` is received +- `on_tick(store, time)` whenever `time > store.time` where `time` is the current Unix time +- `on_block(store, block)` whenever a block `block: SignedBeaconBlock` is received +- `on_attestation(store, attestation)` whenever an attestation `attestation` is received + +Any of the above handlers that trigger an unhandled exception (e.g. a failed assert or an out-of-range list access) are considered invalid. Invalid calls to handlers must not modify `store`. *Notes*: From 8697b30eea5c1353c79919b1940ed158cc69de8a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 15 Jun 2020 15:27:37 +0800 Subject: [PATCH 101/236] Fix configuration --- configs/mainnet/phase1.yaml | 8 ++++++++ configs/minimal/phase1.yaml | 14 +++++++++++--- specs/phase1/beacon-chain.md | 34 ++++++++++++++-------------------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/configs/mainnet/phase1.yaml b/configs/mainnet/phase1.yaml index 399192113a..c3a4f93b9a 100644 --- a/configs/mainnet/phase1.yaml +++ b/configs/mainnet/phase1.yaml @@ -28,6 +28,10 @@ TARGET_SHARD_BLOCK_SIZE: 262144 SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] # len(SHARD_BLOCK_OFFSETS) MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**12 (= 4,096) +BYTES_PER_CUSTODY_CHUNK: 4096 +# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) +CUSTODY_RESPONSE_DEPTH: 8 # Gwei values # 2**14 (= 16,384) Gwei @@ -41,6 +45,10 @@ ONLINE_PERIOD: 8 # 2**8 (= 256) | epochs LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# Max operations per block +# 2**20 (= 1,048,576) +MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576 + # Domain types DOMAIN_SHARD_PROPOSAL: 0x80000000 DOMAIN_SHARD_COMMITTEE: 0x81000000 diff --git a/configs/minimal/phase1.yaml b/configs/minimal/phase1.yaml index 68e60aa87d..5e570d6467 100644 --- a/configs/minimal/phase1.yaml +++ b/configs/minimal/phase1.yaml @@ -30,6 +30,10 @@ TARGET_SHARD_BLOCK_SIZE: 262144 SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233] # len(SHARD_BLOCK_OFFSETS) MAX_SHARD_BLOCKS_PER_ATTESTATION: 12 +# 2**12 (= 4,096) +BYTES_PER_CUSTODY_CHUNK: 4096 +# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK) +CUSTODY_RESPONSE_DEPTH: 8 # Gwei values # 2**14 (= 16,384) Gwei @@ -43,6 +47,10 @@ ONLINE_PERIOD: 8 # 2**8 (= 256) | epochs LIGHT_CLIENT_COMMITTEE_PERIOD: 256 +# Max operations per block +# 2**20 (= 1,048,576) +MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576 + # Domain types DOMAIN_SHARD_PROPOSAL: 0x80000000 DOMAIN_SHARD_COMMITTEE: 0x81000000 @@ -58,11 +66,11 @@ DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 RANDAO_PENALTY_EPOCHS: 2 # [customized] quicker for testing EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096 -# [customized] quickerquicker for testing +# [customized] quicker for testing EPOCHS_PER_CUSTODY_PERIOD: 8 -# [customized] quickerquicker for testing +# [customized] quicker for testing CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# [customized] quickerquicker for testing +# [customized] quicker for testing CUSTODY_RESPONSE_DEADLINE: 32 # Max operations diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 61e30e1022..dbfbde897e 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -16,6 +16,7 @@ - [Gwei values](#gwei-values) - [Initial values](#initial-values) - [Time parameters](#time-parameters) + - [Max operations per block](#max-operations-per-block) - [Domain types](#domain-types) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) @@ -116,12 +117,14 @@ Configuration is not namespaced. Instead it is strictly an extension; ### Shard block configs -| Name | Value | -| - | - | -| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | -| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | -| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | -| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | +| Name | Value | Unit | +| - | - | - | +| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | bytes | +| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | bytes | +| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | - | +| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | - | +| `BYTES_PER_CUSTODY_CHUNK` | `2**12` (= 4,096) | bytes | +| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - | ### Gwei values @@ -142,20 +145,11 @@ Configuration is not namespaced. Instead it is strictly an extension; | - | - | :-: | :-: | | `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 mins | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | bytes | - | -| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | bytes | - | -| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | list of slot offsets | - | -| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | - | - | -| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | | -| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | | -| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | | | -| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | | -| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | | -| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | | | -| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | | | -| `BYTES_PER_CUSTODY_CHUNK` | `2**12` | bytes | | -| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - | - | -| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | | | + +### Max operations per block +| Name | Value | +| - | - | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | ### Domain types From f62125eaa685b6413dbf99ad60b9c5a86fb82afe Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 15 Jun 2020 06:54:48 -0600 Subject: [PATCH 102/236] add phase 1 on-time aggregation --- specs/phase1/validator.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 9c61989894..3048cf8b7e 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -367,6 +367,39 @@ def get_attestation_signature(state: BeaconState, return bls.Aggregate(signatures) ``` +### Attestation Aggregation + +Some validators are selected to locally aggregate attestations with a similar `attestation_data` to their constructed `attestation` for the assigned `slot`. + +Aggregation selection and the core of this duty are largely unchanged from Phase 0. Any additional components or changes are noted. + +#### Broadcast aggregate + +Note the timing of when to broadcast aggregates is altered in Phase 1+. + +If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) three-fourths of the way through the `slot`-that is, `SECONDS_PER_SLOT * 3 / 4` seconds after the start of `slot`. + +##### `AggregateAndProof` + +`AggregateAndProof` is unchanged other than the contained `Attestation`. + +```python +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + aggregate: Attestation + selection_proof: BLSSignature +``` + +##### `SignedAggregateAndProof` + +`AggregateAndProof` is unchanged other than the contained `AggregateAndProof`. + +```python +class SignedAggregateAndProof(Container): + message: AggregateAndProof + signature: BLSSignature +``` + ### Light client committee In addition to the core beacon chain responsibilities, Phase 1 adds an additional role -- the Light Client Committee -- to aid in light client functionality. From 458d3434979c218fbc2044d8c13e2c5574e5b140 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 15 Jun 2020 21:15:11 +0800 Subject: [PATCH 103/236] Fix configs and put domain types to the same table --- configs/mainnet/phase1.yaml | 3 ++- configs/minimal/phase1.yaml | 3 ++- specs/phase1/beacon-chain.md | 13 +++---------- specs/phase1/custody-game.md | 9 --------- specs/phase1/validator.md | 6 +++++- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/configs/mainnet/phase1.yaml b/configs/mainnet/phase1.yaml index cddbb8dfe5..08cf2317d9 100644 --- a/configs/mainnet/phase1.yaml +++ b/configs/mainnet/phase1.yaml @@ -46,7 +46,8 @@ DOMAIN_SHARD_PROPOSAL: 0x80000000 DOMAIN_SHARD_COMMITTEE: 0x81000000 DOMAIN_LIGHT_CLIENT: 0x82000000 DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 - +DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000 +DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000 # custody-game # --------------------------------------------------------------- diff --git a/configs/minimal/phase1.yaml b/configs/minimal/phase1.yaml index 556ff2618f..c519a7427c 100644 --- a/configs/minimal/phase1.yaml +++ b/configs/minimal/phase1.yaml @@ -48,7 +48,8 @@ DOMAIN_SHARD_PROPOSAL: 0x80000000 DOMAIN_SHARD_COMMITTEE: 0x81000000 DOMAIN_LIGHT_CLIENT: 0x82000000 DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 - +DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000 +DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000 # custody-game # --------------------------------------------------------------- diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 77cc015c85..dcb02bfb4a 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -141,14 +141,6 @@ Configuration is not namespaced. Instead it is strictly an extension; | - | - | :-: | :-: | | `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 mins | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | | -| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | | -| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | | -| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | | -| `MAX_GASPRICE` | `Gwei(2**14)` (= 16,384) | Gwei | | -| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | | -| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) | | -| `NO_SIGNATURE` | `BLSSignature(b'\x00' * 96)` | | ### Domain types @@ -157,8 +149,9 @@ Configuration is not namespaced. Instead it is strictly an extension; | `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | -| `DOMAIN_LIGHT_SELECTION_PROOF` | `DomainType('0x83000000')` | -| `DOMAIN_LIGHT_AGGREGATE_AND_PROOF` | `DomainType('0x84000000')` | +| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType(0x83000000)` | +| `DOMAIN_LIGHT_SELECTION_PROOF` | `DomainType('0x84000000')` | +| `DOMAIN_LIGHT_AGGREGATE_AND_PROOF` | `DomainType('0x85000000')` | ## Updated containers diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 5f5acd84fe..9f89c336d5 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -15,7 +15,6 @@ - [Time parameters](#time-parameters) - [Max operations per block](#max-operations-per-block) - [Reward and penalty quotients](#reward-and-penalty-quotients) - - [Signature domain types](#signature-domain-types) - [Data structures](#data-structures) - [New Beacon Chain operations](#new-beacon-chain-operations) - [`CustodySlashing`](#custodyslashing) @@ -79,14 +78,6 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) | | `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) | -### Signature domain types - -The following types are defined, mapping into `DomainType` (little endian): - -| Name | Value | -| - | - | -| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType('0x83000000')` | - ## Data structures ### New Beacon Chain operations diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 3048cf8b7e..65510cc14f 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -36,6 +36,10 @@ - [Construct attestation](#construct-attestation) - [Custody bits blocks](#custody-bits-blocks) - [Signature](#signature) + - [Attestation Aggregation](#attestation-aggregation) + - [Broadcast aggregate](#broadcast-aggregate) + - [`AggregateAndProof`](#aggregateandproof) + - [`SignedAggregateAndProof`](#signedaggregateandproof) - [Light client committee](#light-client-committee) - [Preparation](#preparation) - [Light clent vote](#light-clent-vote) @@ -47,7 +51,7 @@ - [Light client vote aggregation](#light-client-vote-aggregation) - [Aggregation selection](#aggregation-selection) - [Construct aggregate](#construct-aggregate) - - [Broadcast aggregate](#broadcast-aggregate) + - [Broadcast aggregate](#broadcast-aggregate-1) - [`LightAggregateAndProof`](#lightaggregateandproof) - [`SignedLightAggregateAndProof`](#signedlightaggregateandproof) From 50ac8ebb0ccebb0ced41e0471498c422db816856 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 15 Jun 2020 21:25:40 +0800 Subject: [PATCH 104/236] Fix DOMAIN_CUSTODY_BIT_SLASHING --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index dcb02bfb4a..436404a052 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -149,7 +149,7 @@ Configuration is not namespaced. Instead it is strictly an extension; | `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` | | `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` | | `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` | -| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType(0x83000000)` | +| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType('0x83000000')` | | `DOMAIN_LIGHT_SELECTION_PROOF` | `DomainType('0x84000000')` | | `DOMAIN_LIGHT_AGGREGATE_AND_PROOF` | `DomainType('0x85000000')` | From 97d50b381bed78e17b3c2f7510182fa38d5de6c1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 15 Jun 2020 08:11:15 -0600 Subject: [PATCH 105/236] minor fixes in config comments --- configs/mainnet.yaml | 2 +- configs/minimal.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index d2ab3cab67..60af0fbbc9 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -204,7 +204,7 @@ CUSTODY_PROBABILITY_EXPONENT: 10 RANDAO_PENALTY_EPOCHS: 2 # 2**15 (= 32,768) epochs, ~146 days EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768 -# 2**14 (= 16,384) epochs ~73 days +# 2**14 (= 16,384) epochs ~73 days EPOCHS_PER_CUSTODY_PERIOD: 16384 # 2**11 (= 2,048) epochs, ~9 days CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 3fe160bbe2..dedb04f59c 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -199,19 +199,19 @@ GASPRICE_ADJUSTMENT_COEFFICIENT: 8 # Phase 1: Custody Game # --------------------------------------------------------------- -# 1/1024 chance of custody bit 1 [customized for faster testing] +# 1/4 chance of custody bit 1 [customized for faster testing] CUSTODY_PROBABILITY_EXPONENT: 2 # Time parameters # 2**1 (= 2) epochs RANDAO_PENALTY_EPOCHS: 2 -# 2**15 (= 32,768) epochs, ~146 days [customized for faster testing] +# 2**7 (= 128) epochs, ~13.7 hours [customized for faster testing] EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128 -# 2**14 (= 16,384) epochs ~73 days [customized for faster testing] +# 2**6 (= 64) epochs ~6.8 hours [customized for faster testing] EPOCHS_PER_CUSTODY_PERIOD: 64 -# 2**11 (= 2,048) epochs, ~9 days [customized for faster testing] +# 2**4 (= 16) epochs, ~1.7 hours [customized for faster testing] CUSTODY_PERIOD_TO_RANDAO_PADDING: 16 -# 2**15 (= 32,768) epochs, ~146 days +# 2**7 (= 128) epochs, ~13.7 hours [customize for faster testing] MAX_CHUNK_CHALLENGE_DELAY: 128 # Max operations From 3ee0761d17206d5cd3f97ef72dea14beeb106eb7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 16 Jun 2020 00:16:39 +0800 Subject: [PATCH 106/236] Add `shard: Shard` field to `AttestationData` --- specs/phase1/beacon-chain.md | 18 ++++++------------ specs/phase1/fork-choice.md | 3 ++- specs/phase1/shard-transition.md | 3 +-- .../test/fork_choice/test_on_attestation.py | 2 +- .../test/fork_choice/test_on_shard_head.py | 2 +- .../eth2spec/test/helpers/attestations.py | 10 ++++++---- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 87de9d34ea..582e2e669b 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -55,7 +55,6 @@ - [`get_indexed_attestation`](#get_indexed_attestation) - [`get_committee_count_delta`](#get_committee_count_delta) - [`get_start_shard`](#get_start_shard) - - [`get_shard`](#get_shard) - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) @@ -165,6 +164,8 @@ class AttestationData(Container): # FFG vote source: Checkpoint target: Checkpoint + # Shard vote + shard: Shard # Current-slot shard block root shard_head_root: Root # Shard transition root @@ -624,16 +625,6 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard: ) ``` -#### `get_shard` - -```python -def get_shard(state: BeaconState, attestation: Attestation) -> Shard: - """ - Return the shard that the given ``attestation`` is attesting. - """ - return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) -``` - #### `get_latest_slot_for_shard` ```python @@ -833,12 +824,15 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint + assert attestation.data.shard == compute_shard_from_committee_index( + state, attestation.data.index, attestation.data.slot) + # Type 1: on-time attestations, the custody bits should be non-empty. if attestation.custody_bits_blocks != []: # Ensure on-time attestation assert is_on_time_attestation(state, attestation) # Correct data root count - shard = get_shard(state, attestation) + shard = attestation.data.shard assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md index 3f9fbdbfba..b07f334964 100644 --- a/specs/phase1/fork-choice.md +++ b/specs/phase1/fork-choice.md @@ -47,7 +47,8 @@ class LatestMessage(object): def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: target = attestation.data.target beacon_block_root = attestation.data.beacon_block_root - shard = get_shard(store.block_states[beacon_block_root], attestation) + # TODO: separate shard chain vote + shard = attestation.data.shard for i in attesting_indices: if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: store.latest_messages[i] = LatestMessage( diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index c62b059eeb..186b4cad09 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -148,8 +148,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, # 2. Check if the shard state transition result is wrong between # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. if offset_index == 0: - shard = get_shard(beacon_state, attestation) - shard_states = beacon_parent_block.body.shard_transitions[shard].shard_states + shard_states = beacon_parent_block.body.shard_transitions[attestation.data.shard].shard_states shard_state = shard_states[len(shard_states) - 1] else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index a5334c5c78..1c177a9195 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -31,7 +31,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True): latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, - shard=spec.get_shard(state, attestation), + shard=attestation.data.shard, shard_root=attestation.data.shard_head_root, ) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index 24eeaedbee..c84f073b24 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -82,7 +82,7 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer) shard_transition=shard_transition, signed=False, ) - assert spec.get_shard(state, attestation) == shard + assert attestation.data.shard == shard beacon_block.body.attestations = [attestation] beacon_block.body.shard_transitions = shard_transitions diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 1e0560405b..25d55f3a9c 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -45,7 +45,7 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', state -def build_attestation_data(spec, state, slot, index, shard_transition=None, on_time=True): +def build_attestation_data(spec, state, slot, index, shard=None, shard_transition=None, on_time=True): assert state.slot >= slot if slot == state.slot: @@ -77,13 +77,15 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t ) if spec.fork == PHASE1: + if shard is None: + shard = spec.compute_shard_from_committee_index(state, attestation_data.index, attestation_data.slot) + attestation_data.shard = shard + if shard_transition is not None: lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: - # No shard transition -> no shard block - shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) if on_time: shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[]) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 @@ -96,7 +98,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False): - shard = spec.get_shard(state, attestation) + shard = spec.compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) offset_slots = spec.compute_offset_slots( spec.get_latest_slot_for_shard(state, shard), attestation.data.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY, From 01a69288b6dfa94f28f51c525e6869e41eb19164 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 15 Jun 2020 15:33:05 -0600 Subject: [PATCH 107/236] custody 0.01 testing cleanup --- .../eth2spec/test/helpers/attestations.py | 18 +- .../pyspec/eth2spec/test/helpers/custody.py | 4 +- .../test_process_custody_slashing.py | 186 +++++------------- 3 files changed, 48 insertions(+), 160 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index b9c91eaa05..07e2270228 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -96,16 +96,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t return attestation_data -def convert_to_valid_on_time_attestation(spec, state, attestation, shard_transition, - signed=False): - if signed: - sign_attestation(spec, state, attestation) - - return attestation - - -def get_valid_on_time_attestation(spec, state, slot=None, index=None, - shard_transition=None, signed=False): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False): ''' Construct on-time attestation for next slot ''' @@ -172,13 +163,6 @@ def get_valid_attestation(spec, # fill the attestation with (optionally filtered) participants, and optionally sign it fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set) - if spec.fork == PHASE1 and on_time: - attestation = convert_to_valid_on_time_attestation( - spec, state, attestation, - shard_transition, - signed=signed, - ) - return attestation diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index edd7ac19fe..4f91e6dc50 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -194,6 +194,6 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust shard_transition = get_shard_transition(spec, start_slot, block_lengths) slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) - shard_transition.shard_data_roots[0] = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) \ - .get_backing().get_left().merkle_root() + block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) + shard_transition.shard_data_roots[0] = block_data.get_backing().get_left().merkle_root() return shard_transition, slashable_test_vector diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py index d1649484e3..ec0bac82d4 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py @@ -53,21 +53,36 @@ def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, c yield 'post', state -@with_all_phases_except(['phase0']) -@spec_state_test -def test_custody_slashing(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 +def run_standard_custody_slashing_test(spec, + state, + shard_lateness=None, + shard=None, + validator_index=None, + block_lengths=None, + slashing_message_data=None, + correct=True, + valid=True): + if shard_lateness is None: + shard_lateness = spec.SLOTS_PER_EPOCH + transition_to(spec, state, state.slot + shard_lateness) + + if shard is None: + shard = 0 + if validator_index is None: + validator_index = spec.get_beacon_committee(state, state.slot, shard)[0] + offset_slots = spec.get_offset_slots(state, shard) + if block_lengths is None: + block_lengths = [2**15 // 3] * len(offset_slots) - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) + shard_transition, slashable_test_vector = get_custody_slashable_shard_transition( + spec, + state.slot, + block_lengths, + custody_secret, + slashable=correct, + ) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -80,154 +95,43 @@ def test_custody_slashing(spec, state): slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) - - -@with_all_phases_except(['phase0']) -@spec_state_test -def test_incorrect_custody_slashing(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret, slashable=False) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + if slashing_message_data is not None: + slashing.message.data = slashing_message_data - slashing = get_valid_custody_slashing(spec, state, attestation, - shard_transition, custody_secret, slashable_test_vector) - - yield from run_custody_slashing_processing(spec, state, slashing, correct=False) + yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct) @with_all_phases_except(['phase0']) @spec_state_test -def test_multiple_epochs_custody(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) +def test_custody_slashing(spec, state): + yield from run_standard_custody_slashing_test(spec, state) @with_all_phases_except(['phase0']) @spec_state_test -def test_many_epochs_custody(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 10) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) +def test_incorrect_custody_slashing(spec, state): + yield from run_standard_custody_slashing_test(spec, state, correct=False) @with_all_phases_except(['phase0']) @spec_state_test -def test_off_chain_attestation(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( - state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) +def test_multiple_epochs_custody(spec, state): + yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - yield from run_custody_slashing_processing(spec, state, slashing, correct=True) +@with_all_phases_except(['phase0']) +@spec_state_test +def test_many_epochs_custody(spec, state): + yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10) @with_all_phases_except(['phase0']) @spec_state_test def test_invalid_custody_slashing(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - - validator_index = spec.get_beacon_committee( + yield from run_standard_custody_slashing_test( + spec, state, - state.slot, - shard, - )[0] - custody_secret = get_custody_secret(spec, state, validator_index) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(spec, state.slot, - [2**15 // 3] * len(offset_slots), custody_secret) - - attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - - slashing.message.data = ByteList[spec.MAX_SHARD_BLOCK_SIZE]() - - yield from run_custody_slashing_processing(spec, state, slashing, valid=False) + slashing_message_data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](), + valid=False, + ) From 55e17fb1f7d01db05b2135c5e302900655e64cb3 Mon Sep 17 00:00:00 2001 From: lsankar4033 Date: Mon, 15 Jun 2020 18:42:15 -0700 Subject: [PATCH 108/236] Add compute_subnet_for_attestation unittest --- .../test/validator/test_validator_unittest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index 0ac1e7b463..09ceebad4d 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -349,6 +349,22 @@ def test_get_attestation_signature(spec, state): ) +@with_all_phases +@spec_state_test +def test_compute_subnet_for_attestation(spec, state): + for committee_idx in range(spec.MAX_COMMITTEES_PER_SLOT): + for slot in range(state.slot, state.slot + spec.SLOTS_PER_EPOCH): + actual_subnet_id = spec.compute_subnet_for_attestation(state, slot, committee_idx) + + slots_since_epoch_start = slot % spec.SLOTS_PER_EPOCH + committees_since_epoch_start = spec.get_committee_count_at_slot( + state, slot) * slots_since_epoch_start + expected_subnet_id = (committees_since_epoch_start + + committee_idx) % spec.ATTESTATION_SUBNET_COUNT + + assert actual_subnet_id == expected_subnet_id + + # Attestation aggregation From 113563176ae2e94ff01d473b310b012338bad79b Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:36:12 +0100 Subject: [PATCH 109/236] Updated .gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index bcd96f8858..ecbeb9a3c3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,13 @@ tests/core/pyspec/test-reports tests/core/pyspec/eth2spec/test_results.xml *.egg-info + +# flake8 config +tox.ini + +# VS code files +.vscode +*.code-workspace + +# npm (for doctoc) +package-lock.json \ No newline at end of file From 2dee432536bf32df86e657464613f6df44ccffb6 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:43:34 +0100 Subject: [PATCH 110/236] Refactor getting Merkle root of data part of ByteList --- tests/core/pyspec/eth2spec/test/helpers/custody.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 4f91e6dc50..1993e566d9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -136,6 +136,12 @@ def build_proof(anchor, leaf_index): return list(reversed(proof)) +def get_block_data_merkle_root(data_as_bytelist): + # To get the Merkle root of the block data, we need the Merkle root without the length Mixing + # The below implements this in the Remerkleable framework + return data_as_bytelist.get_backing().get_left().merkle_root() + + def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, challenge_index, invalid_chunk_data=False): custody_data = get_custody_test_vector(block_length) @@ -160,8 +166,8 @@ def get_custody_test_vector(bytelength, offset=0): def get_shard_transition(spec, start_slot, block_lengths): - b = [ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)) - .get_backing().get_left().merkle_root() for x in block_lengths] + b = [get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) + for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, shard_block_lengths=block_lengths, @@ -195,5 +201,5 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) - shard_transition.shard_data_roots[0] = block_data.get_backing().get_left().merkle_root() + shard_transition.shard_data_roots[0] = get_block_data_merkle_root(block_data) return shard_transition, slashable_test_vector From a4c2950c4a8bfb28dad81b18db3526d72c9637ca Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:48:00 +0100 Subject: [PATCH 111/236] Phase 1 validator guide stub. This is to start collecting important details with correct validator operation. --- specs/phase1/validator.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 specs/phase1/validator.md diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md new file mode 100644 index 0000000000..310be3129c --- /dev/null +++ b/specs/phase1/validator.md @@ -0,0 +1,39 @@ +# Ethereum 2.0 Phase 1 -- Updates to honest validator + +**Notice**: This document is a work-in-progress for researchers and implementers. This is so far only a skeleton that describes non-obvious pitfalls so that they won't be forgotten when the full version of the document is prepared + +## Table of contents + + + + + + +- [Introduction](#introduction) +- [How to avoid slashing](#how-to-avoid-slashing) + - [Custody slashing](#custody-slashing) + + + + +## Introduction + +This is an update to the [Phase 0 -- Honest validator](../phase0/validator.md) honest validator guide. This will only describe the differences in phase 1. All behaviours in phase 0 remain valid + +## How to avoid slashing + +### Custody slashing + +To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret: + +```python +def get_custody_secret(spec, state, validator_index, epoch=None): + period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None + else spec.get_current_epoch(state)) + epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index) + domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) + signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) + return bls.Sign(privkeys[validator_index], signing_root) +``` + +Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated. While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing. From 8186594dfecb74ae006a57cdd928195ffe1752b2 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 14:57:06 +0100 Subject: [PATCH 112/236] Rename to get_sample_shard_transition --- .../pyspec/eth2spec/test/helpers/custody.py | 4 ++-- .../test_process_chunk_challenge.py | 22 +++++++++---------- .../test_process_challenge_deadlines.py | 4 ++-- .../test_process_custody_final_updates.py | 6 ++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 1993e566d9..f63a07099d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -165,7 +165,7 @@ def get_custody_test_vector(bytelength, offset=0): return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength] -def get_shard_transition(spec, start_slot, block_lengths): +def get_sample_shard_transition(spec, start_slot, block_lengths): b = [get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) for x in block_lengths] shard_transition = spec.ShardTransition( @@ -197,7 +197,7 @@ def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=Tr def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True): - shard_transition = get_shard_transition(spec, start_slot, block_lengths) + shard_transition = get_sample_shard_transition(spec, start_slot, block_lengths) slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py index 94b1c3b110..bdb4325fd3 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py @@ -1,7 +1,7 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, - get_shard_transition, + get_sample_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -70,7 +70,7 @@ def test_challenge_appended(spec, state): transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -91,7 +91,7 @@ def test_challenge_empty_element_replaced(spec, state): transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -114,7 +114,7 @@ def test_duplicate_challenge(spec, state): transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -137,7 +137,7 @@ def test_second_challenge(spec, state): transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -163,7 +163,7 @@ def test_multiple_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -185,7 +185,7 @@ def test_many_epochs_custody(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -207,7 +207,7 @@ def test_off_chain_attestation(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -225,7 +225,7 @@ def test_custody_response(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -253,7 +253,7 @@ def test_custody_response_multiple_epochs(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -281,7 +281,7 @@ def test_custody_response_many_epochs(spec, state): shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py index 6590d8af90..f3675732a4 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py @@ -1,6 +1,6 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, - get_shard_transition, + get_sample_shard_transition, ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -28,7 +28,7 @@ def test_validator_slashed_after_chunk_challenge(spec, state): transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py index b9afe03bfd..6ca6c8c99f 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py @@ -2,7 +2,7 @@ get_valid_chunk_challenge, get_valid_custody_chunk_response, get_valid_custody_key_reveal, - get_shard_transition + get_sample_shard_transition ) from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, @@ -66,7 +66,7 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) @@ -114,7 +114,7 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) transition_to(spec, state, state.slot + 1) shard = 0 offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) + shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True, shard_transition=shard_transition) From 4b8b3f2cbc62c49d8ab9d989164aa0fe079b1213 Mon Sep 17 00:00:00 2001 From: dankrad Date: Tue, 16 Jun 2020 15:05:01 +0100 Subject: [PATCH 113/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 4cf0474054..9b20dea550 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -368,7 +368,6 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge ```python def process_chunk_challenge_response(state: BeaconState, response: CustodyChunkResponse) -> None: - # Get matching challenge (if any) from records matching_challenges = [ record for record in state.custody_chunk_challenge_records From 4bc849bcc2c0deb4e1bba67264d75e614bf5e146 Mon Sep 17 00:00:00 2001 From: dankrad Date: Tue, 16 Jun 2020 15:05:12 +0100 Subject: [PATCH 114/236] Update specs/phase1/custody-game.md Co-authored-by: Hsiao-Wei Wang --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 9b20dea550..8920becda9 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -373,7 +373,7 @@ def process_chunk_challenge_response(state: BeaconState, record for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index ] - assert len(matching_challenges) > 0 + assert len(matching_challenges) == 1 challenge = matching_challenges[0] # Verify chunk index assert response.chunk_index == challenge.chunk_index From df1a9325347dee7991127653346498677e9eecdf Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 15:16:20 +0100 Subject: [PATCH 115/236] Rename misleading variable all_secrets_are_revealed --- specs/phase1/custody-game.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 8920becda9..94d739ba91 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -595,13 +595,13 @@ def process_custody_final_updates(state: BeaconState) -> None: validator_indices_in_records = set([record.responder_index for record in records]) for index, validator in enumerate(state.validators): if validator.exit_epoch != FAR_FUTURE_EPOCH: - all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH - if index in validator_indices_in_records or all_secrets_are_revealed: + not_all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH + if index in validator_indices_in_records or not_all_secrets_are_revealed: # Delay withdrawable epochs if challenge records are not empty or not all # custody secrets revealed validator.withdrawable_epoch = FAR_FUTURE_EPOCH else: - # Reset withdrawable epochs if challenge records are empty + # Reset withdrawable epochs if challenge records are empty and all secrets are revealed if validator.withdrawable_epoch == FAR_FUTURE_EPOCH: validator.withdrawable_epoch = Epoch(validator.all_custody_secrets_revealed_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) From 5e5a951c6f85c2fd886f3707eb013b1e70fdb9eb Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 16 Jun 2020 23:49:41 +0800 Subject: [PATCH 116/236] Fix conflicts --- configs/mainnet/phase0.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index 7677e939bf..39cfddf770 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -151,9 +151,3 @@ DOMAIN_DEPOSIT: 0x03000000 DOMAIN_VOLUNTARY_EXIT: 0x04000000 DOMAIN_SELECTION_PROOF: 0x05000000 DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 -<<<<<<< HEAD:configs/mainnet.yaml -# Phase 1 -DOMAIN_SHARD_PROPOSAL: 0x80000000 -DOMAIN_SHARD_COMMITTEE: 0x81000000 -DOMAIN_LIGHT_CLIENT: 0x82000000 -DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000 From 58935c19d6e8590cf69be6e6e6246e946668e044 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 17:23:09 +0100 Subject: [PATCH 117/236] Move constant --- specs/phase1/beacon-chain.md | 5 ----- specs/phase1/custody-game.md | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index a3f9ec4e9d..59e0bb6da7 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -142,11 +142,6 @@ Configuration is not namespaced. Instead it is strictly an extension; | `ONLINE_PERIOD` | `OnlineEpochs(2**3)` (= 8) | online epochs | ~51 mins | | `LIGHT_CLIENT_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | -### Max operations per block -| Name | Value | -| - | - | -| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | - ### Domain types | Name | Value | diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 94d739ba91..3d4645068f 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -79,6 +79,7 @@ This document details the beacon chain additions and changes in Phase 1 of Ether | Name | Value | | - | - | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) | | `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) | | `MAX_EARLY_DERIVED_SECRET_REVEALS` | `2**0` (= 1) | | `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) | From e6e694fad995affe9a79cd92cf6e81e0e4874617 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Tue, 16 Jun 2020 17:35:33 +0100 Subject: [PATCH 118/236] Fix toc --- specs/phase1/beacon-chain.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 59e0bb6da7..3206a50deb 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -16,7 +16,6 @@ - [Gwei values](#gwei-values) - [Initial values](#initial-values) - [Time parameters](#time-parameters) - - [Max operations per block](#max-operations-per-block) - [Domain types](#domain-types) - [Updated containers](#updated-containers) - [Extended `AttestationData`](#extended-attestationdata) From c761c437d4cb4a647a5c65b704731c86f0883385 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Jun 2020 12:11:47 -0600 Subject: [PATCH 119/236] revert e128 and c901 in lint --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 419500ebbf..da87f79126 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --ignore=E252,W504,W503,E128,C901 --max-line-length=120 ./eth2spec \ + flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec \ && cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \ && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1; From ace46c38a729e26db2be4212f2120801c3adc720 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Jun 2020 15:33:25 -0600 Subject: [PATCH 120/236] revert local config settings from .gitignore --- .gitignore | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.gitignore b/.gitignore index ecbeb9a3c3..bcd96f8858 100644 --- a/.gitignore +++ b/.gitignore @@ -28,13 +28,3 @@ tests/core/pyspec/test-reports tests/core/pyspec/eth2spec/test_results.xml *.egg-info - -# flake8 config -tox.ini - -# VS code files -.vscode -*.code-workspace - -# npm (for doctoc) -package-lock.json \ No newline at end of file From 2e0950560b7759024d2cf7239b112698d8b147a5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Jun 2020 16:03:36 -0600 Subject: [PATCH 121/236] PR feedback --- specs/phase1/shard-fork-choice.md | 2 +- specs/phase1/validator.md | 17 ++++++++--------- .../pyspec/eth2spec/test/helpers/shard_block.py | 2 -- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 0607613e8d..e051054fe2 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -124,7 +124,7 @@ def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ beacon_head_state = store.block_states[beacon_head_root] latest_shard_block_root = beacon_head_state.shard_states[shard].latest_block_root - shard_head_root = get_shard_head(store, shard_store) + shard_head_root = tget_shard_head(store, shard_store) root = shard_head_root shard_blocks = [] while root != latest_shard_block_root: diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 65510cc14f..6db20e82e4 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -95,7 +95,7 @@ Lookahead for beacon committee assignments operates in the same manner as Phase Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a validator should: * Let `shard = compute_shard_from_committee_index(committe_index)` -* Subscribe to the pubsub topic `shard_{shard}_shard_block` (attestation subnet peers should have this topic available). +* Subscribe to the pubsub topic `shard_{shard}_block` (attestation subnet peers should have this topic available). ## Beacon chain responsibilities @@ -140,22 +140,21 @@ Specifically: * Let `full_transitions` be a dictionary mapping from the `shard_transition_root`s found in `attestations` to the corresponding full `ShardTransition` * Then for each `shard` and `winning_root` in `zip(shards, winning_roots)` set `shard_transitions[shard] = full_transitions[winning_root]` -*Note*: The `state` passed into `get_shard_winning_roots` must be transitioned into the epoch of `block.slot` to run accurately due to the internal use of `get_online_validator_indices`. +*Note*: The `state` passed into `get_shard_winning_roots` must be transitioned the slot of `block.slot` to run accurately due to the internal use of `get_online_validator_indices` and `is_on_time_attestation`. ```python def get_shard_winning_roots(state: BeaconState, - slot: Slot, attestations: Sequence[Attestation]) -> Tuple[Sequence[Shard], Sequence[Root]]: shards = [] winning_roots = [] online_indices = get_online_validator_indices(state) - committee_count = get_committee_count_at_slot(state, slot) + committee_count = get_committee_count_at_slot(state, state.slot) for committee_index in map(CommitteeIndex, range(committee_count)): - shard = compute_shard_from_committee_index(state, committee_index, slot) - # All attestations in the block for this committee/shard and current slot + shard = compute_shard_from_committee_index(state, committee_index, state.slot) + # All attestations in the block for this committee/shard and are "on time" shard_attestations = [ attestation for attestation in attestations - if attestation.data.index == committee_index and attestation.data.slot == slot + if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] committee = get_beacon_committee(state, state.slot, committee_index) @@ -259,7 +258,7 @@ A validator should create and broadcast the `attestation` to the associated atte - Let `head_block` be the result of running the fork choice during the assigned slot. - Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. - Let `shard_head_block` be the result of running the fork choice on the assigned shard chain during the assigned slot. -- Let `shard_blocks` be the shard blocks in the chain starting immediately _after_ the most recent crosslink (`head_state.shard_transitions[shard].latest_block_root`) up to the `shard_head_block`. +- Let `shard_blocks` be the shard blocks in the chain starting immediately _after_ the most recent crosslink (`head_state.shard_transitions[shard].latest_block_root`) up to the `shard_head_block` (i.e. the value of the shard fork choice store of `get_pending_shard_blocks(store, shard_store)`). *Note*: We assume that the fork choice only follows branches with valid `offset_slots` with respect to the most recent beacon state shard transition for the queried shard. @@ -420,7 +419,7 @@ If the validator is in the next light client committee, they must join the `ligh def is_in_next_light_client_committee(state: BeaconState, index: ValidatorIndex) -> bool: current_source_epoch = compute_committee_source_epoch(get_current_epoch(state), LIGHT_CLIENT_COMMITTEE_PERIOD) next_source_epoch = current_source_epoch + LIGHT_CLIENT_COMMITTEE_PERIOD - next_committee = get_light_client_committee(state, next_source_epoch) + next_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD) return index in next_committee ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 15443e3860..ddf66f6c23 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -62,8 +62,6 @@ def get_shard_transitions(spec, parent_beacon_state, shard_blocks): on_time_slot, ) len_offset_slots = len(offset_slots) - # TODO this is actually unsafe for long offset_slots - assert len_offset_slots == on_time_slot - parent_beacon_state.shard_states[shard].slot - 1 shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks) if len(blocks) > 0: From c8ae7d99f9e4875581ab3f31d88f63ab63a11ab6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 17 Jun 2020 09:05:44 +0800 Subject: [PATCH 122/236] Fix after auto-merge --- specs/phase1/beacon-chain.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0b7db55163..ad543a9417 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -760,6 +760,9 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint + assert attestation.data.shard == compute_shard_from_committee_index( + state, attestation.data.index, attestation.data.slot) + # Type 1: on-time attestations if is_on_time_attestation(state, attestation): # Correct parent block root From 23cbbb1d081c07c915e8db56de796e1cb042bb98 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 17 Jun 2020 09:47:39 +0800 Subject: [PATCH 123/236] Fix shard number assertion and refactor it --- specs/phase1/beacon-chain.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index ad543a9417..f63654182c 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -760,13 +760,14 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint - assert attestation.data.shard == compute_shard_from_committee_index( - state, attestation.data.index, attestation.data.slot) - # Type 1: on-time attestations if is_on_time_attestation(state, attestation): # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) + # Correct shard number + assert attestation.data.shard == compute_shard_from_committee_index( + state, attestation.data.index, attestation.data.slot + ) # Type 2: no shard transition else: # Ensure delayed attestation @@ -868,7 +869,6 @@ def process_crosslink_for_shard(state: BeaconState, on_time_attestation_slot = compute_previous_slot(state.slot) committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) online_indices = get_online_validator_indices(state) - shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) # Loop over all shard transition roots shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) @@ -894,7 +894,7 @@ def process_crosslink_for_shard(state: BeaconState, assert shard_transition_root == hash_tree_root(shard_transition) # Apply transition - apply_shard_transition(state, shard, shard_transition) + apply_shard_transition(state, attestation.data.shard, shard_transition) # Apply proposer reward and cost beacon_proposer_index = get_beacon_proposer_index(state) estimated_attester_reward = sum([get_base_reward(state, attester) for attester in transition_participants]) @@ -902,11 +902,11 @@ def process_crosslink_for_shard(state: BeaconState, increase_balance(state, beacon_proposer_index, proposer_reward) states_slots_lengths = zip( shard_transition.shard_states, - get_offset_slots(state, shard), + get_offset_slots(state, attestation.data.shard), shard_transition.shard_block_lengths ) for shard_state, slot, length in states_slots_lengths: - proposer_index = get_shard_proposer_index(state, slot, shard) + proposer_index = get_shard_proposer_index(state, slot, attestation.data.shard) decrease_balance(state, proposer_index, shard_state.gasprice * length) # Return winning transition root @@ -931,13 +931,16 @@ def process_crosslinks(state: BeaconState, attestation for attestation in attestations if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] - shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) - winning_root = process_crosslink_for_shard(state, committee_index, shard_transitions[shard], shard_attestations) - if winning_root != Root(): - # Mark relevant pending attestations as creating a successful crosslink - for pending_attestation in state.current_epoch_attestations: - if is_winning_attestation(state, pending_attestation, committee_index, winning_root): - pending_attestation.crosslink_success = True + if len(shard_attestations) > 0: + shard = shard_attestations[0].data.shard + winning_root = process_crosslink_for_shard( + state, committee_index, shard_transitions[shard], shard_attestations + ) + if winning_root != Root(): + # Mark relevant pending attestations as creating a successful crosslink + for pending_attestation in state.current_epoch_attestations: + if is_winning_attestation(state, pending_attestation, committee_index, winning_root): + pending_attestation.crosslink_success = True ``` ###### `verify_empty_shard_transition` From fbf10a0db3155ae413248a9241104c4755fd7db4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Jun 2020 22:29:28 -0600 Subject: [PATCH 124/236] fix tests --- specs/phase1/validator.md | 43 +------------------ .../test/validator/test_validator_unittest.py | 28 +----------- 2 files changed, 4 insertions(+), 67 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index aef8fbb99c..558a1b3bf2 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -34,8 +34,6 @@ - [Head shard root](#head-shard-root) - [Shard transition](#shard-transition) - [Construct attestation](#construct-attestation) - - [Custody bits blocks](#custody-bits-blocks) - - [Signature](#signature) - [Attestation Aggregation](#attestation-aggregation) - [Broadcast aggregate](#broadcast-aggregate) - [`AggregateAndProof`](#aggregateandproof) @@ -216,7 +214,7 @@ Packaging into a `SignedBeaconBlock` is unchanged from Phase 0. A validator is expected to create, sign, and broadcast an attestation during each epoch. -Assignments and the core of this duty are unchanged from Phase 0. There are a few additional fields related to the assigned shard chain and custody bit. +Assignments and the core of this duty are unchanged from Phase 0. There are a few additional fields related to the assigned shard chain. The `Attestation` and `AttestationData` defined in the [Phase 1 Beacon Chain spec]() utilizes `shard_transition_root: Root` rather than a full `ShardTransition`. For the purposes of the validator and p2p layer, a modified `FullAttestationData` and containing `FullAttestation` are used to send the accompanying `ShardTransition` in its entirety. Note that due to the properties of SSZ `hash_tree_root`, the root and signatures of `AttestationData` and `FullAttestationData` are equivalent. @@ -243,7 +241,6 @@ class FullAttestationData(Container): class FullAttestation(Container): aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] data: FullAttestationData - custody_bits_blocks: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION] signature: BLSSignature ``` @@ -334,43 +331,7 @@ def get_shard_transition(beacon_state: BeaconState, Next, the validator creates `attestation`, a `FullAttestation` as defined above. -`attestation.data` and `attestation.aggregation_bits` are unchanged from Phase 0. - -##### Custody bits blocks - -- Let `attestation.custody_bits_blocks` be a the value returned by `get_custody_bits_blocks()` - -```python -def get_custody_bits_blocks() -> List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_SHARD_BLOCKS_PER_ATTESTATION]: - pass -``` - -##### Signature - -Set `attestation.signature = attestation_signature` where `attestation_signature` is obtained from: - -```python -def get_attestation_signature(state: BeaconState, - attestation: Attestation, - privkey: int) -> BLSSignature: - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) - attestation_data_root = hash_tree_root(attestation.data) - index_in_committee = attestation.aggregation_bits.index(True) - signatures = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - custody_bit = custody_bits[index_in_committee] - signing_root = compute_signing_root( - AttestationCustodyBitWrapper( - attestation_data_root=attestation_data_root, - block_index=block_index, - bit=custody_bit, - ), - domain, - ) - signatures.append(bls.Sign(privkey, signing_root)) - - return bls.Aggregate(signatures) -``` +`attestation.data`, `attestation.aggregation_bits`, and `attestation.signature` are unchanged from Phase 0. But safety/validity in signing the message is premised upon calculation of the "custody bit" [TODO]. ### Attestation Aggregation diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index 26affd579e..8edd4fd2ae 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -1,8 +1,6 @@ -from random import Random - from eth2spec.test.context import ( spec_state_test, - always_bls, with_phases, with_all_phases, with_all_phases_except, + always_bls, with_phases, with_all_phases, PHASE0, ) from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation @@ -323,7 +321,7 @@ def test_get_block_signature(spec, state): # Attesting -@with_phases([PHASE0]) +@with_all_phases @spec_state_test @always_bls def test_get_attestation_signature_phase0(spec, state): @@ -343,28 +341,6 @@ def test_get_attestation_signature_phase0(spec, state): ) -@with_all_phases_except([PHASE0]) -@spec_state_test -@always_bls -def test_get_attestation_signature_phase1plus(spec, state): - privkey = privkeys[0] - - def single_participant(comm): - rng = Random(1100) - return rng.sample(comm, 1) - - attestation = get_valid_attestation(spec, state, filter_participant_set=single_participant, signed=False) - indexed_attestation = spec.get_indexed_attestation(state, attestation) - - assert indexed_attestation.attestation.aggregation_bits.count(True) == 1 - - # Cannot use normal `run_get_signature_test` due to complex signature type - index_in_committee = indexed_attestation.attestation.aggregation_bits.index(True) - privkey = privkeys[indexed_attestation.committee[index_in_committee]] - attestation.signature = spec.get_attestation_signature(state, attestation, privkey) - assert spec.verify_attestation_custody(state, spec.get_indexed_attestation(state, attestation)) - - # Attestation aggregation From 9b60a9b799496f1a2fc941bf1250b7009483ed1d Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 17 Jun 2020 20:04:40 +0200 Subject: [PATCH 125/236] Avoid state usage in p2p validation, compute committee count per slot for epoch as a whole --- setup.py | 6 +++--- specs/phase0/beacon-chain.md | 13 ++++++------- specs/phase0/p2p-interface.md | 12 ++++++++---- specs/phase0/validator.md | 19 +++++++++++++------ specs/phase1/beacon-chain.md | 7 ++++--- specs/phase1/validator.md | 2 +- .../test/fork_choice/test_get_head.py | 2 +- .../eth2spec/test/helpers/attestations.py | 6 +++--- .../eth2spec/test/helpers/shard_block.py | 2 +- .../test_process_attestation.py | 2 +- ..._process_justification_and_finalization.py | 2 +- .../phase_1/unittests/test_get_start_shard.py | 15 ++++++++++----- .../test/validator/test_validator_unittest.py | 11 +++++------ 13 files changed, 57 insertions(+), 42 deletions(-) diff --git a/setup.py b/setup.py index 6bee87080d..cc3b00da93 100644 --- a/setup.py +++ b/setup.py @@ -189,10 +189,10 @@ def wrapper(*args, **kw): # type: ignore lambda state, index: (state.validators.hash_tree_root(), state.slot, index), _get_base_reward, lru_size=2048) -_get_committee_count_at_slot = get_committee_count_at_slot -get_committee_count_at_slot = cache_this( +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_at_slot, lru_size=SLOTS_PER_EPOCH * 3) + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) _get_active_validator_indices = get_active_validator_indices get_active_validator_indices = cache_this( diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 7914dc4583..6533dcb7f3 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -89,7 +89,7 @@ - [`get_active_validator_indices`](#get_active_validator_indices) - [`get_validator_churn_limit`](#get_validator_churn_limit) - [`get_seed`](#get_seed) - - [`get_committee_count_at_slot`](#get_committee_count_at_slot) + - [`get_committee_count_per_slot`](#get_committee_count_per_slot) - [`get_beacon_committee`](#get_beacon_committee) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - [`get_total_balance`](#get_total_balance) @@ -948,14 +948,13 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes return hash(domain_type + int_to_bytes(epoch, length=8) + mix) ``` -#### `get_committee_count_at_slot` +#### `get_committee_count_per_slot` ```python -def get_committee_count_at_slot(state: BeaconState, slot: Slot) -> uint64: +def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: """ - Return the number of committees at ``slot``. + Return the number of committees in each ``slot`` for the given epoch. """ - epoch = compute_epoch_at_slot(slot) return max(1, min( MAX_COMMITTEES_PER_SLOT, len(get_active_validator_indices(state, epoch)) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, @@ -970,7 +969,7 @@ def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) Return the beacon committee at ``slot`` for ``index``. """ epoch = compute_epoch_at_slot(slot) - committees_per_slot = get_committee_count_at_slot(state, slot) + committees_per_slot = get_committee_count_per_slot(state, epoch) return compute_committee( indices=get_active_validator_indices(state, epoch), seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), @@ -1720,7 +1719,7 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data - assert data.index < get_committee_count_at_slot(state, data.slot) + assert data.index < get_committee_count_per_slot(state, data.target.epoch) assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) assert data.target.epoch == compute_epoch_at_slot(data.slot) assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 6c4cdd2a68..211508f00f 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -275,18 +275,22 @@ Additional global topics are used to propagate lower frequency validator message Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are: - `beacon_attestation_{subnet_id}` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet. - - _[REJECT]_ The attestation is for the correct subnet (i.e. `compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index) == subnet_id`). + - _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be computed along with the committee information for the signature check. - _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). - - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len(get_attesting_indices(state, attestation.data, attestation.aggregation_bits)) == 1`). + - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. - _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. - _[REJECT]_ The signature of `attestation` is valid. #### Attestations and Aggregation -Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. `beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1. +Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. +The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. +`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1. +The subnets are rotated through with `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` subnets per slot. + +Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)}` as `Attestation`s. -Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.index)}` as `Attestation`s. Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `AggregateAndProof`s. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 35c52f3468..e325f75f61 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -172,8 +172,9 @@ def get_committee_assignment(state: BeaconState, assert epoch <= next_epoch start_slot = compute_start_slot_at_epoch(epoch) + committee_count_per_slot = get_committee_count_per_slot(state, epoch) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): - for index in range(get_committee_count_at_slot(state, Slot(slot))): + for index in range(committee_count_per_slot): committee = get_beacon_committee(state, Slot(slot), CommitteeIndex(index)) if validator_index in committee: return committee, CommitteeIndex(index), Slot(slot) @@ -199,8 +200,10 @@ The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahe Specifically a validator should: * Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments. -* Find peers of the pubsub topic `beacon_attestation_{compute_subnet_for_attestation(state, slot, committee_index)}`. - * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][compute_subnet_for_attestation(state, slot, committee_index)] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. +* Calculate the committees per slot for the next epoch: `committees_per_slot = get_committee_count_per_slot(state, next_epoch)` +* Calculate the subnet index: `subnet_id = compute_subnet_for_attestation(committees_per_slot, slot, committee_index)` +* Find peers of the pubsub topic `beacon_attestation_{subnet_id}`. + * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][subnet_id] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. * If the validator is assigned to be an aggregator for the slot (see `is_aggregator()`), then subscribe to the topic. *Note*: If the validator is _not_ assigned to be an aggregator, the validator only needs sufficient number of peers on the topic to be able to publish messages. The validator does not need to _subscribe_ and listen to all messages on the topic. @@ -425,16 +428,20 @@ def get_attestation_signature(state: BeaconState, attestation_data: AttestationD #### Broadcast attestation -Finally, the validator broadcasts `attestation` to the associated attestation subnet -- the `beacon_attestation_{compute_subnet_for_attestation(state, attestation.data.slot, attestation.data.committee_index)}` pubsub topic. +Finally, the validator broadcasts `attestation` to the associated attestation subnet, the `beacon_attestation_{subnet_id}` pubsub topic. + +The `subnet_id` for the `attestation` is calculated with: +- Let `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`. +- Let `subnet_id = compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.committee_index)`. ```python -def compute_subnet_for_attestation(state: BeaconState, slot: Slot, committee_index: CommitteeIndex) -> uint64: +def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: """ Compute the correct subnet for an attestation for Phase 0. Note, this mimics expected Phase 1 behavior where attestations will be mapped to their shard subnet. """ slots_since_epoch_start = slot % SLOTS_PER_EPOCH - committees_since_epoch_start = get_committee_count_at_slot(state, slot) * slots_since_epoch_start + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start return (committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT ``` diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4e4778abef..472182a45e 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -592,7 +592,8 @@ def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: S """ Return the sum of committee counts in range ``[start_slot, stop_slot)``. """ - return sum(get_committee_count_at_slot(state, Slot(slot)) for slot in range(start_slot, stop_slot)) + return sum(get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(slot))) + for slot in range(start_slot, stop_slot)) ``` #### `get_start_shard` @@ -757,7 +758,7 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: ```python def validate_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data - assert data.index < get_committee_count_at_slot(state, data.slot) + assert data.index < get_committee_count_per_slot(state, data.target.epoch) assert data.index < get_active_shard_count(state) assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) assert data.target.epoch == compute_epoch_at_slot(data.slot) @@ -935,7 +936,7 @@ def process_crosslinks(state: BeaconState, shard_transitions: Sequence[ShardTransition], attestations: Sequence[Attestation]) -> None: on_time_attestation_slot = compute_previous_slot(state.slot) - committee_count = get_committee_count_at_slot(state, on_time_attestation_slot) + committee_count = get_committee_count_per_slot(state, compute_epoch_at_slot(on_time_attestation_slot)) for committee_index in map(CommitteeIndex, range(committee_count)): # All attestations in the block for this committee/shard and current slot shard_attestations = [ diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 558a1b3bf2..66445abb85 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -148,7 +148,7 @@ def get_shard_winning_roots(state: BeaconState, shards = [] winning_roots = [] online_indices = get_online_validator_indices(state) - committee_count = get_committee_count_at_slot(state, state.slot) + committee_count = get_committee_count_per_slot(state, get_current_epoch(state)) for committee_index in map(CommitteeIndex, range(committee_count)): shard = compute_shard_from_committee_index(state, committee_index, state.slot) # All attestations in the block for this committee/shard and are "on time" diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py index f9a7398047..859fc797f0 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py @@ -154,7 +154,7 @@ def test_filtered_block_tree(spec, state): attestations = [] for i in range(spec.SLOTS_PER_EPOCH): slot = rogue_block.slot + i - for index in range(spec.get_committee_count_at_slot(non_viable_state, slot)): + for index in range(spec.get_committee_count_per_slot(non_viable_state, spec.compute_epoch_at_slot(slot))): attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True) attestations.append(attestation) diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 23eba34da8..5016ac5dfc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -257,7 +257,7 @@ def next_epoch_with_attestations(spec, block = build_empty_block_for_next_slot(spec, post_state) if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 - committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) + committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): for index in range(committees_per_slot): if spec.fork == PHASE1: @@ -275,7 +275,7 @@ def next_epoch_with_attestations(spec, if fill_prev_epoch: slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 - committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) + committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) for index in range(committees_per_slot): prev_attestation = get_valid_attestation( spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) @@ -304,7 +304,7 @@ def prepare_state_with_attestations(spec, state, participation_fn=None): for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): # create an attestation for each index in each slot in epoch if state.slot < next_epoch_start_slot: - for committee_index in range(spec.get_committee_count_at_slot(state, state.slot)): + for committee_index in range(spec.get_committee_count_per_slot(state, spec.get_current_epoch(state))): def temp_participants_filter(comm): if participation_fn is None: return comm diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index ddf66f6c23..6957f22831 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -75,7 +75,7 @@ def get_shard_transitions(spec, parent_beacon_state, shard_blocks): def get_committee_index_of_shard(spec, state, slot, shard): # Optional[CommitteeIndex] active_shard_count = spec.get_active_shard_count(state) - committee_count = spec.get_committee_count_at_slot(state, slot) + committee_count = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot)) start_shard = spec.get_start_shard(state, slot) for committee_index in range(committee_count): if (start_shard + committee_index) % active_shard_count == shard: diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py index 5de8913d6e..28fd9ac9e2 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py @@ -139,7 +139,7 @@ def test_wrong_index_for_committee_signature(spec, state): @spec_state_test @never_bls def test_wrong_index_for_slot(spec, state): - while spec.get_committee_count_at_slot(state, state.slot) >= spec.MAX_COMMITTEES_PER_SLOT: + while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT: state.validators = state.validators[:len(state.validators) // 2] state.balances = state.balances[:len(state.balances) // 2] diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py index 09af2126db..73091a1c78 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py @@ -27,8 +27,8 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support remaining_balance = int(total_balance * 2 // 3) # can become negative start_slot = spec.compute_start_slot_at_epoch(epoch) + committees_per_slot = spec.get_committee_count_per_slot(state, epoch) for slot in range(start_slot, start_slot + spec.SLOTS_PER_EPOCH): - committees_per_slot = spec.get_committee_count_at_slot(state, slot) for index in range(committees_per_slot): # Check if we already have had sufficient balance. (and undone if we don't want it). # If so, do not create more attestations. (we do not have empty pending attestations normally anyway) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py index 27afd4a4e8..c443545b9b 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py @@ -10,11 +10,16 @@ @spec_state_test def test_get_committee_count_delta(spec, state): assert spec.get_committee_count_delta(state, 0, 0) == 0 - assert spec.get_committee_count_at_slot(state, 0) != 0 - assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_at_slot(state, 0) - assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_at_slot(state, 1) - assert spec.get_committee_count_delta(state, 0, 2) == ( - spec.get_committee_count_at_slot(state, 0) + spec.get_committee_count_at_slot(state, 1) + assert spec.get_committee_count_per_slot(state, 0) != 0 + assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_per_slot(state, 0) + assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_per_slot(state, 0) + assert spec.get_committee_count_delta(state, 0, 2) == spec.get_committee_count_per_slot(state, 0) * 2 + assert spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) == ( + spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH + ) + assert spec.get_committee_count_delta(state, 0, 2 * spec.SLOTS_PER_EPOCH) == ( + spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH + + spec.get_committee_count_per_slot(state, 1) * spec.SLOTS_PER_EPOCH ) diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index e749b1e3d0..84c4b9fe58 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -26,7 +26,7 @@ def run_get_committee_assignment(spec, state, epoch, validator_index, valid=True committee, committee_index, slot = assignment assert spec.compute_epoch_at_slot(slot) == epoch assert committee == spec.get_beacon_committee(state, slot, committee_index) - assert committee_index < spec.get_committee_count_at_slot(state, slot) + assert committee_index < spec.get_committee_count_per_slot(state, epoch) assert validator_index in committee assert valid except AssertionError: @@ -359,13 +359,12 @@ def test_get_attestation_signature_phase0(spec, state): def test_compute_subnet_for_attestation(spec, state): for committee_idx in range(spec.MAX_COMMITTEES_PER_SLOT): for slot in range(state.slot, state.slot + spec.SLOTS_PER_EPOCH): - actual_subnet_id = spec.compute_subnet_for_attestation(state, slot, committee_idx) + committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot)) + actual_subnet_id = spec.compute_subnet_for_attestation(committees_per_slot, slot, committee_idx) slots_since_epoch_start = slot % spec.SLOTS_PER_EPOCH - committees_since_epoch_start = spec.get_committee_count_at_slot( - state, slot) * slots_since_epoch_start - expected_subnet_id = (committees_since_epoch_start + - committee_idx) % spec.ATTESTATION_SUBNET_COUNT + committees_since_epoch_start = committees_per_slot * slots_since_epoch_start + expected_subnet_id = (committees_since_epoch_start + committee_idx) % spec.ATTESTATION_SUBNET_COUNT assert actual_subnet_id == expected_subnet_id From ce0371a66b408c86af5305527a9bd2bd253899e0 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 17 Jun 2020 20:23:26 +0200 Subject: [PATCH 126/236] fix comment: committees per slot for given *epoch* --- specs/phase0/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 6533dcb7f3..cd52762d56 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -953,7 +953,7 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes ```python def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: """ - Return the number of committees in each ``slot`` for the given epoch. + Return the number of committees in each slot for the given ``epoch``. """ return max(1, min( MAX_COMMITTEES_PER_SLOT, From 3eabcddb4caa540abaf7c8721cb76b1f64918dde Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 17 Jun 2020 13:24:00 -0700 Subject: [PATCH 127/236] Update validator.md --- specs/phase1/validator.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 558a1b3bf2..530bda004e 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 0 -- Honest Validator +# Ethereum 2.0 Phase 1 -- Honest Validator **Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 1](./), which describes the expected actions of a "validator" participating in the Ethereum 2.0 Phase 1 protocol. @@ -99,7 +99,7 @@ Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a ## Beacon chain responsibilities -A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch. +A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attesting). Proposals happen infrequently, whereas attestations should be created once per epoch. These responsibilities are largely unchanged from Phase 0, but utilize the updated `SignedBeaconBlock`, `BeaconBlock`, `BeaconBlockBody`, `Attestation`, and `AttestationData` definitions found in Phase 1. Below notes only the additional and modified behavior with respect to Phase 0. @@ -109,7 +109,7 @@ Phase 1 adds light client committees and associated responsibilities, discussed #### Preparing for a `BeaconBlock` -`slot`, `proposer_index`, `parent_root` fields are unchanged. +`slot`, `proposer_index`, `parent_root`, `state_root` fields are unchanged. #### Constructing the `BeaconBlockBody` @@ -133,7 +133,7 @@ Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./custody ##### Shard transitions -Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot. +Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain.md#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot. Specifically: * Call `shards, winning_roots = get_shard_winning_roots(state, block.slot, block.body.attestations)` @@ -216,7 +216,7 @@ A validator is expected to create, sign, and broadcast an attestation during eac Assignments and the core of this duty are unchanged from Phase 0. There are a few additional fields related to the assigned shard chain. -The `Attestation` and `AttestationData` defined in the [Phase 1 Beacon Chain spec]() utilizes `shard_transition_root: Root` rather than a full `ShardTransition`. For the purposes of the validator and p2p layer, a modified `FullAttestationData` and containing `FullAttestation` are used to send the accompanying `ShardTransition` in its entirety. Note that due to the properties of SSZ `hash_tree_root`, the root and signatures of `AttestationData` and `FullAttestationData` are equivalent. +The `Attestation` and `AttestationData` defined in the [Phase 1 Beacon Chain spec](./beacon-chain.md) utilizes `shard_transition_root: Root` rather than a full `ShardTransition`. For the purposes of the validator and p2p layer, a modified `FullAttestationData` and containing `FullAttestation` are used to send the accompanying `ShardTransition` in its entirety. Note that due to the properties of SSZ `hash_tree_root`, the root and signatures of `AttestationData` and `FullAttestationData` are equivalent. #### `FullAttestationData` From 3f765f55caab64d4c0f5f93f5d2bdc4efd45d568 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 17 Jun 2020 22:34:43 +0200 Subject: [PATCH 128/236] Fix deprecated utility code, avoid wrong helper function name, add tests --- ssz/merkle-proofs.md | 42 +++++++++++------ .../eth2spec/utils/test_merkle_proof_util.py | 47 +++++++++++++++++++ 2 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py diff --git a/ssz/merkle-proofs.md b/ssz/merkle-proofs.md index 44b85fdda8..2f32e43eb9 100644 --- a/ssz/merkle-proofs.md +++ b/ssz/merkle-proofs.md @@ -26,25 +26,34 @@ ## Helper functions ```python -def get_next_power_of_two(x: int) -> int: +def get_power_of_two_ceil(x: int) -> int: """ - Get next power of 2 >= the input. + Get the power of 2 for given input, or the closest higher power of 2 if the input is not a power of 2. + Commonly used for "how many nodes do I need for a bottom tree layer fitting x elements?" + Example: 0->1, 1->1, 2->2, 3->4, 4->4, 5->8, 6->8, 7->8, 8->8, 9->16. """ - if x <= 2: - return x + if x <= 1: + return 1 + elif x == 2: + return 2 else: - return 2 * get_next_power_of_two((x + 1) // 2) + return 2 * get_power_of_two_ceil((x + 1) // 2) ``` ```python -def get_previous_power_of_two(x: int) -> int: +def get_power_of_two_floor(x: int) -> int: """ - Get the previous power of 2 >= the input. + Get the power of 2 for given input, or the closest lower power of 2 if the input is not a power of 2. + The zero case is a placeholder and not used for math with generalized indices. + Commonly used for "what power of two makes up the root bit of the generalized index?" + Example: 0->1, 1->1, 2->2, 3->2, 4->4, 5->4, 6->4, 7->4, 8->8, 9->8 """ - if x <= 2: + if x <= 1: + return 1 + if x == 2: return x else: - return 2 * get_previous_power_of_two(x // 2) + return 2 * get_power_of_two_floor(x // 2) ``` ## Generalized Merkle tree index @@ -62,9 +71,14 @@ Note that the generalized index has the convenient property that the two childre ```python def merkle_tree(leaves: Sequence[Bytes32]) -> Sequence[Bytes32]: - padded_length = get_next_power_of_two(len(leaves)) - o = [Bytes32()] * padded_length + list(leaves) + [Bytes32()] * (padded_length - len(leaves)) - for i in range(padded_length - 1, 0, -1): + """ + Return an array representing the tree nodes by generalized index: + [0, 1, 2, 3, 4, 5, 6, 7], where each layer is a power of 2. The 0 index is ignored. The 1 index is the root. + The result will be twice the size as the padded bottom layer for the input leaves. + """ + bottom_length = get_power_of_two_ceil(len(leaves)) + o = [Bytes32()] * bottom_length + list(leaves) + [Bytes32()] * (bottom_length - len(leaves)) + for i in range(bottom_length - 1, 0, -1): o[i] = hash(o[i * 2] + o[i * 2 + 1]) return o ``` @@ -169,7 +183,7 @@ def get_generalized_index(typ: SSZType, path: Sequence[Union[int, SSZVariableNam else: pos, _, _ = get_item_position(typ, p) base_index = (GeneralizedIndex(2) if issubclass(typ, (List, ByteList)) else GeneralizedIndex(1)) - root = GeneralizedIndex(root * base_index * get_next_power_of_two(chunk_count(typ)) + pos) + root = GeneralizedIndex(root * base_index * get_power_of_two_ceil(chunk_count(typ)) + pos) typ = get_elem_type(typ, p) return root ``` @@ -188,7 +202,7 @@ def concat_generalized_indices(*indices: GeneralizedIndex) -> GeneralizedIndex: """ o = GeneralizedIndex(1) for i in indices: - o = GeneralizedIndex(o * get_previous_power_of_two(i) + (i - get_previous_power_of_two(i))) + o = GeneralizedIndex(o * get_power_of_two_floor(i) + (i - get_power_of_two_floor(i))) return o ``` diff --git a/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py b/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py new file mode 100644 index 0000000000..e1d59fa8c3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py @@ -0,0 +1,47 @@ +import pytest + + +# Note: these functions are extract from merkle-proofs.md (deprecated), +# the tests are temporary to show correctness while the document is still there. + +def get_power_of_two_ceil(x: int) -> int: + if x <= 1: + return 1 + elif x == 2: + return 2 + else: + return 2 * get_power_of_two_ceil((x + 1) // 2) + + +def get_power_of_two_floor(x: int) -> int: + if x <= 1: + return 1 + if x == 2: + return x + else: + return 2 * get_power_of_two_floor(x // 2) + + +power_of_two_ceil_cases = [ + (0, 1), (1, 1), (2, 2), (3, 4), (4, 4), (5, 8), (6, 8), (7, 8), (8, 8), (9, 16), +] + +power_of_two_floor_cases = [ + (0, 1), (1, 1), (2, 2), (3, 2), (4, 4), (5, 4), (6, 4), (7, 4), (8, 8), (9, 8), +] + + +@pytest.mark.parametrize( + 'value,expected', + power_of_two_ceil_cases, +) +def test_get_power_of_two_ceil(value, expected): + assert get_power_of_two_ceil(value) == expected + + +@pytest.mark.parametrize( + 'value,expected', + power_of_two_floor_cases, +) +def test_get_power_of_two_floor(value, expected): + assert get_power_of_two_floor(value) == expected From 17c86177d9b1b0ed4963dff5c4cb2e2dc44a6ee0 Mon Sep 17 00:00:00 2001 From: ericsson Date: Thu, 18 Jun 2020 01:43:41 +0300 Subject: [PATCH 129/236] Change types of BeaconBlockBody chunk_challenges and chunk_challenge_responses to List[CustodyChunkChallenge, ...] --- specs/phase1/beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4e4778abef..a84d432a1e 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -252,8 +252,8 @@ class BeaconBlockBody(Container): deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] # Custody game - chunk_challenges: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGES] - chunk_challenge_responses: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES] + chunk_challenges: List[CustodyChunkChallenge, MAX_CUSTODY_CHUNK_CHALLENGES] + chunk_challenge_responses: List[CustodyChunkChallenge, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES] custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS] early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS] custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] From adced70c5425adee09ddf9b09cf2731ed5c6ccdd Mon Sep 17 00:00:00 2001 From: ericsson Date: Thu, 18 Jun 2020 02:04:16 +0300 Subject: [PATCH 130/236] use `aggregate.data` instead of hust `data` --- specs/phase1/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 558a1b3bf2..15596edc6c 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -195,7 +195,7 @@ def get_best_light_client_aggregate(block: BeaconBlock, aggregates: Sequence[LightClientVote]) -> LightClientVote: viable_aggregates = [ aggregate for aggregate in aggregates - if aggregate.slot == compute_previous_slot(block.slot) and aggregate.beacon_block_root == block.parent_root + if aggregate.data.slot == compute_previous_slot(block.slot) and aggregate.data.beacon_block_root == block.parent_root ] return max( From cc84b49d133233e5879c4503b4df2eda58fa642f Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 17 Jun 2020 16:20:46 -0700 Subject: [PATCH 131/236] on_time_attestation_slot instead of current_slot --- specs/phase1/validator.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 558a1b3bf2..65cd23cd8e 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -148,15 +148,16 @@ def get_shard_winning_roots(state: BeaconState, shards = [] winning_roots = [] online_indices = get_online_validator_indices(state) - committee_count = get_committee_count_at_slot(state, state.slot) + on_time_attestation_slot = compute_previous_slot(state.slot) + committee_count = get_committee_count_at_slot(state, on_time_attestation_slot) for committee_index in map(CommitteeIndex, range(committee_count)): - shard = compute_shard_from_committee_index(state, committee_index, state.slot) + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) # All attestations in the block for this committee/shard and are "on time" shard_attestations = [ attestation for attestation in attestations if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] - committee = get_beacon_committee(state, state.slot, committee_index) + committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) # Loop over all shard transition roots, looking for a winning root shard_transition_roots = set([a.data.shard_transition_root for a in shard_attestations]) From e479e964808e771f2aeb7ede747aa137845f119a Mon Sep 17 00:00:00 2001 From: ericsson Date: Thu, 18 Jun 2020 02:46:06 +0300 Subject: [PATCH 132/236] lint fix --- specs/phase1/validator.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 15596edc6c..ea69f03899 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -195,7 +195,10 @@ def get_best_light_client_aggregate(block: BeaconBlock, aggregates: Sequence[LightClientVote]) -> LightClientVote: viable_aggregates = [ aggregate for aggregate in aggregates - if aggregate.data.slot == compute_previous_slot(block.slot) and aggregate.data.beacon_block_root == block.parent_root + if ( + aggregate.data.slot == compute_previous_slot(block.slot) + and aggregate.data.beacon_block_root == block.parent_root + ) ] return max( From 9dc43957e2228df37e3b9260de65a09f4c9bd53c Mon Sep 17 00:00:00 2001 From: ericsson Date: Thu, 18 Jun 2020 03:39:51 +0300 Subject: [PATCH 133/236] a fix according to @protolambda comments --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/custody-game.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index a84d432a1e..cd938e0c5f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -253,7 +253,7 @@ class BeaconBlockBody(Container): voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] # Custody game chunk_challenges: List[CustodyChunkChallenge, MAX_CUSTODY_CHUNK_CHALLENGES] - chunk_challenge_responses: List[CustodyChunkChallenge, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES] + chunk_challenge_responses: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES] custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS] early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS] custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS] diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 124167a358..6b767193ac 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -302,7 +302,7 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - fn(state, operation) for_ops(body.chunk_challenges, process_chunk_challenge) - for_ops(body.chunk_challenge_responses, process_chunk_challenge) + for_ops(body.chunk_challenge_responses, process_chunk_challenge_response) for_ops(body.custody_key_reveals, process_custody_key_reveal) for_ops(body.early_derived_secret_reveals, process_early_derived_secret_reveal) for_ops(body.custody_slashings, process_custody_slashing) From 974ef47c03dc137659b24185d20713ac1dc589ba Mon Sep 17 00:00:00 2001 From: Joanne Fuller Date: Thu, 18 Jun 2020 14:47:10 +1000 Subject: [PATCH 134/236] Clarification of types. --- ssz/simple-serialize.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index b8a6bc9a20..700a428e86 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -66,6 +66,8 @@ * **union**: union type containing one of the given subtypes * notation `Union[type_0, type_1, ...]`, e.g. `union[null, uint64]` +*Note*: Both `Vector[boolean, N]` and `Bitvector[N]` are valid, yet distinct due to their different serialization requirements. Similarly, both `List[boolean, N]` and `Bitlist[N]` are valid, yet distinct. Generally `Bitvector[N]`/`Bitlist[N]` are preferred because of their serialization efficiencies. + ### Variable-size and fixed-size We recursively define "variable-size" types to be lists, unions, `Bitlist` and all types that contain a variable-size type. All other types are said to be "fixed-size". @@ -88,9 +90,9 @@ Assuming a helper function `default(type)` which returns the default value for ` | `boolean` | `False` | | `Container` | `[default(type) for type in container]` | | `Vector[type, N]` | `[default(type)] * N` | -| `Bitvector[boolean, N]` | `[False] * N` | +| `Bitvector[N]` | `[False] * N` | | `List[type, N]` | `[]` | -| `Bitlist[boolean, N]` | `[]` | +| `Bitlist[N]` | `[]` | | `Union[type_0, type_1, ...]` | `default(type_0)` | #### `is_zero` From 4428a6aedffc66162edec92431ce0b127b5874d5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 13:45:32 +0800 Subject: [PATCH 135/236] Fix table --- specs/phase0/beacon-chain.md | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 74c09a0c57..f8357be966 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -218,9 +218,9 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | Duration | | - | - | :-: | :-: | -<<<<<<< HEAD -| `MIN_GENESIS_DELAY` | `uint64(86400)` | seconds | 1 day | +| `GENESIS_DELAY` | `uint64(172800)` | seconds | 2 days | | `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | +| `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | | `SLOTS_PER_EPOCH` | `uint64(2**5)` (= 32) | slots | 6.4 minutes | | `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes | @@ -230,20 +230,6 @@ The following values are (non-configurable) constants used throughout the specif | `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | | `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | -======= -| `GENESIS_DELAY` | `172800` | seconds | 2 days | -| `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds | -| `SECONDS_PER_ETH1_BLOCK` | `14` | seconds | 14 seconds | -| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds | -| `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 | -| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `2**5` (= 32) | epochs | ~3.4 hours | -| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours | -| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | -| `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours | ->>>>>>> dev ### State list lengths From 26c540fbf9fa0dceb7b09210f82af6e7a8b71c84 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 14:07:59 +0800 Subject: [PATCH 136/236] Fix `get_min_new_period_epochs` helper --- .../eth2spec/test/validator/test_validator_unittest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py index e749b1e3d0..f5346e4e1a 100644 --- a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py @@ -40,9 +40,9 @@ def run_is_candidate_block(spec, eth1_block, period_start, success=True): def get_min_new_period_epochs(spec): - return int( - spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2 # to seconds - / spec.SECONDS_PER_SLOT / spec.SLOTS_PER_EPOCH + return ( + (spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2) # to seconds + // spec.SECONDS_PER_SLOT // spec.SLOTS_PER_EPOCH ) From 51e7969147f992e72be8a25e5fe61b64059feb89 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 14:28:56 +0800 Subject: [PATCH 137/236] Set linter configs in `linter.ini` --- Makefile | 9 ++++----- linter.ini | 13 +++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 linter.ini diff --git a/Makefile b/Makefile index da87f79126..d890924839 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,8 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/phas COV_HTML_OUT=.htmlcov COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html +LINTER_CONFIG_FILE = $(CURDIR)/linter.ini + .PHONY: clean partial_clean all test citest lint generate_tests pyspec install_test open_cov \ install_deposit_contract_tester test_deposit_contract install_deposit_contract_compiler \ compile_deposit_contract test_compile_deposit_contract check_toc @@ -101,9 +103,8 @@ codespell: lint: pyspec . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec \ - && cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \ - && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1; + flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \ + && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.phase1 install_deposit_contract_tester: cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt @@ -123,8 +124,6 @@ test_compile_deposit_contract: cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \ python3.7 -m pytest . -CURRENT_DIR = ${CURDIR} - # Runs a generator, identified by param 1 define run_generator # Started! diff --git a/linter.ini b/linter.ini new file mode 100644 index 0000000000..6575642f17 --- /dev/null +++ b/linter.ini @@ -0,0 +1,13 @@ +[flake8] +ignore = E252,W504,W503 +max-line-length = 120 + +[mypy] +disallow_incomplete_defs = True +disallow_untyped_defs = True + +warn_unused_ignores = True +warn_unused_configs = True +warn_redundant_casts = True + +ignore_missing_imports = True From 27eb2e25793e764afa21e683c413e26196d03be5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 14:54:27 +0800 Subject: [PATCH 138/236] Move testing files from */phase_0 to */phase0, */phase_1 to */phase1 --- tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/__init__.py | 0 .../test/{phase_0 => phase0}/block_processing/__init__.py | 0 .../block_processing/test_process_attestation.py | 0 .../block_processing/test_process_attester_slashing.py | 0 .../block_processing/test_process_block_header.py | 0 .../{phase_0 => phase0}/block_processing/test_process_deposit.py | 0 .../block_processing/test_process_proposer_slashing.py | 0 .../block_processing/test_process_voluntary_exit.py | 0 .../test/{phase_0 => phase0}/epoch_processing/__init__.py | 0 .../epoch_processing/run_epoch_process_base.py | 0 .../epoch_processing/test_process_final_updates.py | 0 .../test_process_justification_and_finalization.py | 0 .../epoch_processing/test_process_registry_updates.py | 0 .../epoch_processing/test_process_rewards_and_penalties.py | 0 .../epoch_processing/test_process_slashings.py | 0 .../pyspec/eth2spec/test/{phase_0 => phase0}/rewards/__init__.py | 0 .../eth2spec/test/{phase_0 => phase0}/rewards/test_basic.py | 0 .../pyspec/eth2spec/test/{phase_0 => phase0}/rewards/test_leak.py | 0 .../eth2spec/test/{phase_0 => phase0}/rewards/test_random.py | 0 .../pyspec/eth2spec/test/{phase_0 => phase0}/sanity/__init__.py | 0 .../eth2spec/test/{phase_0 => phase0}/sanity/test_blocks.py | 0 .../pyspec/eth2spec/test/{phase_0 => phase0}/sanity/test_slots.py | 0 tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/__init__.py | 0 .../test/{phase_1 => phase1}/block_processing/__init__.py | 0 .../block_processing/test_process_attestation.py | 0 .../block_processing/test_process_chunk_challenge.py | 0 .../block_processing/test_process_custody_key_reveal.py | 0 .../block_processing/test_process_custody_slashing.py | 0 .../block_processing/test_process_early_derived_secret_reveal.py | 0 .../block_processing/test_process_shard_transition.py | 0 .../epoch_processing/test_process_challenge_deadlines.py | 0 .../epoch_processing/test_process_custody_final_updates.py | 0 .../epoch_processing/test_process_reveal_deadlines.py | 0 .../pyspec/eth2spec/test/{phase_1 => phase1}/sanity/__init__.py | 0 .../eth2spec/test/{phase_1 => phase1}/sanity/test_blocks.py | 0 .../eth2spec/test/{phase_1 => phase1}/unittests/__init__.py | 0 .../test/{phase_1 => phase1}/unittests/test_get_start_shard.py | 0 37 files changed, 0 insertions(+), 0 deletions(-) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/test_process_attestation.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/test_process_attester_slashing.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/test_process_block_header.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/test_process_deposit.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/test_process_proposer_slashing.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/block_processing/test_process_voluntary_exit.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/run_epoch_process_base.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/test_process_final_updates.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/test_process_justification_and_finalization.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/test_process_registry_updates.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/test_process_rewards_and_penalties.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/epoch_processing/test_process_slashings.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/rewards/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/rewards/test_basic.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/rewards/test_leak.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/rewards/test_random.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/sanity/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/sanity/test_blocks.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_0 => phase0}/sanity/test_slots.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/test_process_attestation.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/test_process_chunk_challenge.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/test_process_custody_key_reveal.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/test_process_custody_slashing.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/test_process_early_derived_secret_reveal.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/block_processing/test_process_shard_transition.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/epoch_processing/test_process_challenge_deadlines.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/epoch_processing/test_process_custody_final_updates.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/epoch_processing/test_process_reveal_deadlines.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/sanity/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/sanity/test_blocks.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/unittests/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{phase_1 => phase1}/unittests/test_get_start_shard.py (100%) diff --git a/tests/core/pyspec/eth2spec/test/phase_0/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/__init__.py rename to tests/core/pyspec/eth2spec/test/phase0/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/__init__.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attestation.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_attester_slashing.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_block_header.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_deposit.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_proposer_slashing.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/block_processing/test_process_voluntary_exit.py rename to tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/__init__.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/run_epoch_process_base.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/run_epoch_process_base.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/run_epoch_process_base.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_final_updates.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_justification_and_finalization.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_registry_updates.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_rewards_and_penalties.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/epoch_processing/test_process_slashings.py rename to tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/__init__.py rename to tests/core/pyspec/eth2spec/test/phase0/rewards/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_basic.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/test_basic.py rename to tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_leak.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/test_leak.py rename to tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/rewards/test_random.py rename to tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/sanity/__init__.py rename to tests/core/pyspec/eth2spec/test/phase0/sanity/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py rename to tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_slots.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_0/sanity/test_slots.py rename to tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/__init__.py rename to tests/core/pyspec/eth2spec/test/phase1/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/__init__.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_attestation.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_chunk_challenge.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_key_reveal.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_key_reveal.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_custody_slashing.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_early_derived_secret_reveal.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_early_derived_secret_reveal.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_early_derived_secret_reveal.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py rename to tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_challenge_deadlines.py rename to tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_custody_final_updates.py rename to tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/epoch_processing/test_process_reveal_deadlines.py rename to tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/sanity/__init__.py rename to tests/core/pyspec/eth2spec/test/phase1/sanity/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py rename to tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py rename to tests/core/pyspec/eth2spec/test/phase1/unittests/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py rename to tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py From ec25f7863244c5c7173e69851fc0f10cc079918f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 14:55:50 +0800 Subject: [PATCH 139/236] Update path --- .../test_process_final_updates.py | 2 +- ..._process_justification_and_finalization.py | 2 +- .../test_process_registry_updates.py | 2 +- .../test_process_rewards_and_penalties.py | 7 +++--- .../test_process_slashings.py | 2 +- .../test/phase0/sanity/test_blocks.py | 14 +++++------ .../test_process_attestation.py | 5 ++-- .../test_process_chunk_challenge.py | 23 ++++++++++--------- .../test_process_custody_slashing.py | 13 ++++++----- .../test_process_challenge_deadlines.py | 9 ++++---- .../test_process_custody_final_updates.py | 19 ++++++++------- .../test_process_reveal_deadlines.py | 9 ++++---- .../pyspec/eth2spec/test/test_finality.py | 4 ++-- tests/generators/epoch_processing/main.py | 2 +- tests/generators/operations/main.py | 2 +- tests/generators/rewards/main.py | 2 +- tests/generators/sanity/main.py | 2 +- 17 files changed, 64 insertions(+), 55 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py index 8cb09be0ef..27676a59aa 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py @@ -1,5 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import ( +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import ( run_epoch_processing_with, run_epoch_processing_to ) from eth2spec.test.helpers.state import transition_to diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index 09af2126db..a5408c9add 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -1,5 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import ( +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import ( run_epoch_processing_with ) from eth2spec.test.helpers.state import transition_to diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index b6597b1cf6..c734010f32 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,7 +1,7 @@ from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with def run_process_registry_updates(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index bafefcad63..d3744f897f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -1,4 +1,5 @@ from eth2spec.test.context import ( + PHASE0, spec_state_test, spec_test, with_all_phases, with_phases, single_phase, with_custom_state, @@ -16,7 +17,7 @@ ) from eth2spec.test.helpers.rewards import leaking from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with from random import Random @@ -24,7 +25,7 @@ def run_process_rewards_and_penalties(spec, state): yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties') -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_genesis_epoch_no_attestations_no_penalties(spec, state): pre_state = state.copy() @@ -37,7 +38,7 @@ def test_genesis_epoch_no_attestations_no_penalties(spec, state): assert state.balances[index] == pre_state.balances[index] -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_genesis_epoch_full_attestations_no_rewards(spec, state): attestations = [] diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index 23c8ce11ad..5101c75483 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -1,5 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import ( +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import ( run_epoch_processing_with, run_epoch_processing_to ) from eth2spec.test.helpers.state import next_epoch diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 1e057e5a95..c3b1dceed8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -21,8 +21,8 @@ from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.context import ( + PHASE0, PHASE1, spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases, - PHASE1 ) @@ -113,7 +113,7 @@ def process_and_sign_block_without_header_validations(spec, state, block): return sign_block(spec, state, block) -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_proposal_for_genesis_slot(spec, state): assert state.slot == spec.GENESIS_SLOT @@ -484,7 +484,7 @@ def test_duplicate_attester_slashing(spec, state): # All AttesterSlashing tests should be adopted for Phase 1 but helper support is not yet there -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_multiple_attester_slashings_no_overlap(spec, state): # Skip test if config cannot handle multiple AttesterSlashings per block @@ -525,7 +525,7 @@ def test_multiple_attester_slashings_no_overlap(spec, state): check_attester_slashing_effect(spec, pre_state, state, full_indices) -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_multiple_attester_slashings_partial_overlap(spec, state): # Skip test if config cannot handle multiple AttesterSlashings per block @@ -740,7 +740,7 @@ def create_signed_exit(index): # exceeding the minimal-config randao mixes memory size. # Applies to all voluntary-exit sanity block tests. -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_voluntary_exit(spec, state): validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] @@ -768,7 +768,7 @@ def test_voluntary_exit(spec, state): assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_double_validator_exit_same_block(spec, state): validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] @@ -789,7 +789,7 @@ def test_double_validator_exit_same_block(spec, state): yield 'post', None -@with_phases(['phase0']) +@with_phases([PHASE0]) @spec_state_test def test_multiple_different_validator_exits_same_block(spec, state): validator_indices = [ diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py index 34ff284129..a0cf7472fd 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_attestation.py @@ -1,4 +1,5 @@ from eth2spec.test.context import ( + PHASE0, with_all_phases_except, spec_state_test, always_bls, @@ -11,7 +12,7 @@ ) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_on_time_success(spec, state): @@ -22,7 +23,7 @@ def test_on_time_success(spec, state): yield from run_attestation_processing(spec, state, attestation) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test @always_bls def test_late_success(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py index bdb4325fd3..c9adabb62b 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py @@ -8,11 +8,12 @@ ) from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( + PHASE0, with_all_phases_except, spec_state_test, expect_assertion_error, ) -from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=True): @@ -64,7 +65,7 @@ def run_custody_chunk_response_processing(spec, state, custody_response, valid=T yield 'post', state -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_challenge_appended(spec, state): transition_to(spec, state, state.slot + 1) @@ -85,7 +86,7 @@ def test_challenge_appended(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_challenge_empty_element_replaced(spec, state): transition_to(spec, state, state.slot + 1) @@ -108,7 +109,7 @@ def test_challenge_empty_element_replaced(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_duplicate_challenge(spec, state): transition_to(spec, state, state.slot + 1) @@ -131,7 +132,7 @@ def test_duplicate_challenge(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge, valid=False) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_second_challenge(spec, state): transition_to(spec, state, state.slot + 1) @@ -156,7 +157,7 @@ def test_second_challenge(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge1) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_multiple_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) @@ -178,7 +179,7 @@ def test_multiple_epochs_custody(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_many_epochs_custody(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) @@ -200,7 +201,7 @@ def test_many_epochs_custody(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_off_chain_attestation(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) @@ -218,7 +219,7 @@ def test_off_chain_attestation(spec, state): yield from run_chunk_challenge_processing(spec, state, challenge) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_custody_response(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) @@ -246,7 +247,7 @@ def test_custody_response(spec, state): yield from run_custody_chunk_response_processing(spec, state, custody_response) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_custody_response_multiple_epochs(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) @@ -274,7 +275,7 @@ def test_custody_response_multiple_epochs(spec, state): yield from run_custody_chunk_response_processing(spec, state, custody_response) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_custody_response_many_epochs(spec, state): transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py index ec0bac82d4..07cd769961 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py @@ -9,11 +9,12 @@ from eth2spec.utils.ssz.ssz_typing import ByteList from eth2spec.test.helpers.state import get_balance, transition_to from eth2spec.test.context import ( + PHASE0, with_all_phases_except, spec_state_test, expect_assertion_error, ) -from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True): @@ -102,31 +103,31 @@ def run_standard_custody_slashing_test(spec, yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_custody_slashing(spec, state): yield from run_standard_custody_slashing_test(spec, state) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_incorrect_custody_slashing(spec, state): yield from run_standard_custody_slashing_test(spec, state, correct=False) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_multiple_epochs_custody(spec, state): yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_many_epochs_custody(spec, state): yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10) -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_invalid_custody_slashing(spec, state): yield from run_standard_custody_slashing_test( diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py index f3675732a4..675b0d8da6 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py @@ -7,13 +7,14 @@ ) from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( + PHASE0, with_all_phases_except, spec_state_test, ) -from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with -from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( +from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, ) @@ -22,7 +23,7 @@ def run_process_challenge_deadlines(spec, state): yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_slashed_after_chunk_challenge(spec, state): transition_to(spec, state, state.slot + 1) diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py index 6ca6c8c99f..630ddc3a7e 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py @@ -1,3 +1,6 @@ +from eth2spec.test.context import ( + PHASE0, +) from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, @@ -12,21 +15,21 @@ with_all_phases_except, spec_state_test, ) -from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with -from eth2spec.test.phase_1.block_processing.test_process_chunk_challenge import ( +from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, run_custody_chunk_response_processing, ) -from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing +from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing def run_process_custody_final_updates(spec, state): yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates') -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_withdrawal_delay(spec, state): spec.initiate_validator_exit(state, 0) @@ -37,7 +40,7 @@ def test_validator_withdrawal_delay(spec, state): assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): spec.initiate_validator_exit(state, 0) @@ -60,7 +63,7 @@ def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): transition_to(spec, state, state.slot + 1) @@ -108,7 +111,7 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state): transition_to(spec, state, state.slot + 1) diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py index 9cc0069b93..da1a604469 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py @@ -3,18 +3,19 @@ ) from eth2spec.test.helpers.state import transition_to from eth2spec.test.context import ( + PHASE0, with_all_phases_except, spec_state_test, ) -from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with -from eth2spec.test.phase_1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing +from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing def run_process_challenge_deadlines(spec, state): yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_slashed_after_reveal_deadline(spec, state): assert state.validators[0].slashed == 0 @@ -33,7 +34,7 @@ def test_validator_slashed_after_reveal_deadline(spec, state): assert state.validators[0].slashed == 1 -@with_all_phases_except(['phase0']) +@with_all_phases_except([PHASE0]) @spec_state_test def test_validator_not_slashed_after_reveal(spec, state): transition_to(spec, state, spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) diff --git a/tests/core/pyspec/eth2spec/test/test_finality.py b/tests/core/pyspec/eth2spec/test/test_finality.py index 55ccde2d9b..adbadcdf2c 100644 --- a/tests/core/pyspec/eth2spec/test/test_finality.py +++ b/tests/core/pyspec/eth2spec/test/test_finality.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import spec_state_test, never_bls, with_all_phases, with_phases +from eth2spec.test.context import PHASE0, spec_state_test, never_bls, with_all_phases, with_phases from eth2spec.test.helpers.state import next_epoch_via_block from eth2spec.test.helpers.attestations import next_epoch_with_attestations @@ -28,7 +28,7 @@ def check_finality(spec, assert state.finalized_checkpoint == prev_state.finalized_checkpoint -@with_phases(["phase0"]) +@with_phases([PHASE0]) @spec_state_test @never_bls def test_finality_no_updates_at_genesis(spec, state): diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py index f3bbc21e6d..13ac76f419 100644 --- a/tests/generators/epoch_processing/main.py +++ b/tests/generators/epoch_processing/main.py @@ -2,7 +2,7 @@ from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 -from eth2spec.test.phase_0.epoch_processing import ( +from eth2spec.test.phase0.epoch_processing import ( test_process_final_updates, test_process_justification_and_finalization, test_process_registry_updates, diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 935c7aa63d..6d4f6d1395 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -1,6 +1,6 @@ from typing import Iterable -from eth2spec.test.phase_0.block_processing import ( +from eth2spec.test.phase0.block_processing import ( test_process_attestation, test_process_attester_slashing, test_process_block_header, diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index c90943cabe..5d063c4340 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -2,7 +2,7 @@ from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 -from eth2spec.test.phase_0.rewards import ( +from eth2spec.test.phase0.rewards import ( test_basic, test_leak, test_random, diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 89fb89838b..45a1c8c4f5 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -5,7 +5,7 @@ from gen_from_tests.gen import generate_from_tests from eth2spec.test.context import PHASE0 -from eth2spec.test.phase_0.sanity import test_blocks, test_slots +from eth2spec.test.phase0.sanity import test_blocks, test_slots from eth2spec.config import config_util from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 From 7dbecdf9044a65ff438791c90ad7dfc32763f76f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 17:06:38 +0800 Subject: [PATCH 140/236] Fix some missing arguments --- specs/phase1/validator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 1ce98a409a..073458009a 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -94,7 +94,7 @@ Beacon chain validator assignments to beacon committees and beacon block proposa Lookahead for beacon committee assignments operates in the same manner as Phase 0, but committee members must join a shard block pubsub topic in addition to the committee attestation topic. Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a validator should: -* Let `shard = compute_shard_from_committee_index(committe_index)` +* Let `shard = compute_shard_from_committee_index(state, committee_index, slot)` * Subscribe to the pubsub topic `shard_{shard}_block` (attestation subnet peers should have this topic available). ## Beacon chain responsibilities @@ -136,7 +136,7 @@ Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./custody Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot. Specifically: -* Call `shards, winning_roots = get_shard_winning_roots(state, block.slot, block.body.attestations)` +* Call `shards, winning_roots = get_shard_winning_roots(state, block.body.attestations)` * Let `full_transitions` be a dictionary mapping from the `shard_transition_root`s found in `attestations` to the corresponding full `ShardTransition` * Then for each `shard` and `winning_root` in `zip(shards, winning_roots)` set `shard_transitions[shard] = full_transitions[winning_root]` @@ -185,7 +185,7 @@ def get_shard_winning_roots(state: BeaconState, ##### Light client fields -First retrieve `best_aggregate` from `get_best_light_client_aggregate` where `aggregates` is a list of valid aggregated `LightClientVote`s for the previous slot. +First retrieve `best_aggregate` from `get_best_light_client_aggregate(block, aggregates)` where `aggregates` is a list of valid aggregated `LightClientVote`s for the previous slot. Then: * Set `light_client_bits = best_aggregate.aggregation_bits` From 6aca7afd818aa6089128492173ec7856b46b65c4 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 17:20:27 +0800 Subject: [PATCH 141/236] Add notes of active shard count --- specs/phase1/validator.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 073458009a..2c5baa1a5f 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -97,6 +97,8 @@ Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a * Let `shard = compute_shard_from_committee_index(state, committee_index, slot)` * Subscribe to the pubsub topic `shard_{shard}_block` (attestation subnet peers should have this topic available). +TODO: For now, the `state` we pass to `compute_shard_from_committee_index` is the current state without considering `len(state.shard_states)`, i.e., the result from `get_active_shard_count(state)` changes. We should fix it when we have shard count update logic. + ## Beacon chain responsibilities A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch. From 5171a91dfba310bccc2d63cb40ef820c387b6ff0 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 18:55:15 +0800 Subject: [PATCH 142/236] Fix path --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d890924839..5ef25289e8 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,8 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/phas COV_HTML_OUT=.htmlcov COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html -LINTER_CONFIG_FILE = $(CURDIR)/linter.ini +CURRENT_DIR = ${CURDIR} +LINTER_CONFIG_FILE = $(CURRENT_DIR)/linter.ini .PHONY: clean partial_clean all test citest lint generate_tests pyspec install_test open_cov \ install_deposit_contract_tester test_deposit_contract install_deposit_contract_compiler \ From 8cf07742901bdb467591cb98fe99de85433997fe Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 20:34:34 +0800 Subject: [PATCH 143/236] Fix shard_block_length type in `compute_updated_gasprice` --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/shard-transition.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index cd938e0c5f..6e8700e2f7 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -493,7 +493,7 @@ def compute_offset_slots(start_slot: Slot, end_slot: Slot) -> Sequence[Slot]: #### `compute_updated_gasprice` ```python -def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint8) -> Gwei: +def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint64) -> Gwei: if shard_block_length > TARGET_SHARD_BLOCK_SIZE: delta = (prev_gasprice * (shard_block_length - TARGET_SHARD_BLOCK_SIZE) // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 24c39aa3d2..083064225b 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -70,8 +70,9 @@ def shard_state_transition(shard_state: ShardState, """ shard_state.slot = block.slot prev_gasprice = shard_state.gasprice - shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body)) - if len(block.body) == 0: + shard_block_length = len(block.body) + shard_state.gasprice = compute_updated_gasprice(prev_gasprice, uint64(shard_block_length)) + if shard_block_length == 0: latest_block_root = shard_state.latest_block_root else: latest_block_root = hash_tree_root(block) From 35bf3b5290bed6bbae03ca62d798766dbcd3791c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 23:20:24 +0800 Subject: [PATCH 144/236] Refactor `validate_attestation` Co-authored-by: Danny Ryan --- specs/phase1/beacon-chain.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index f63654182c..27f317bc18 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -765,9 +765,8 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) # Correct shard number - assert attestation.data.shard == compute_shard_from_committee_index( - state, attestation.data.index, attestation.data.slot - ) + shard = compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) + assert attestation.data.shard == shard # Type 2: no shard transition else: # Ensure delayed attestation From fe47869d5f73ba5c4aea419186fad8e47b9ced63 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 18 Jun 2020 23:32:23 +0800 Subject: [PATCH 145/236] Revert `process_crosslinks` and add comment --- specs/phase1/beacon-chain.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 27f317bc18..485a52d06f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -926,20 +926,20 @@ def process_crosslinks(state: BeaconState, committee_count = get_committee_count_at_slot(state, on_time_attestation_slot) for committee_index in map(CommitteeIndex, range(committee_count)): # All attestations in the block for this committee/shard and current slot + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) + # Since the attestations are validated, all `shard_attestations` satisfy `attestation.data.shard == shard` shard_attestations = [ attestation for attestation in attestations if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] - if len(shard_attestations) > 0: - shard = shard_attestations[0].data.shard - winning_root = process_crosslink_for_shard( - state, committee_index, shard_transitions[shard], shard_attestations - ) - if winning_root != Root(): - # Mark relevant pending attestations as creating a successful crosslink - for pending_attestation in state.current_epoch_attestations: - if is_winning_attestation(state, pending_attestation, committee_index, winning_root): - pending_attestation.crosslink_success = True + winning_root = process_crosslink_for_shard( + state, committee_index, shard_transitions[shard], shard_attestations + ) + if winning_root != Root(): + # Mark relevant pending attestations as creating a successful crosslink + for pending_attestation in state.current_epoch_attestations: + if is_winning_attestation(state, pending_attestation, committee_index, winning_root): + pending_attestation.crosslink_success = True ``` ###### `verify_empty_shard_transition` From bd61c2686b79f1ea56c8c9db14577702fb108cae Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 18 Jun 2020 09:45:10 -0600 Subject: [PATCH 146/236] PR feedback --- specs/phase0/p2p-interface.md | 2 +- specs/phase1/beacon-chain.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 211508f00f..96bf856da7 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -275,7 +275,7 @@ Additional global topics are used to propagate lower frequency validator message Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are: - `beacon_attestation_{subnet_id}` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet. - - _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be computed along with the committee information for the signature check. + - _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be pre-computed along with the committee information for the signature check. - _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 493f96f1df..ed01af6eb4 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -593,8 +593,10 @@ def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: S """ Return the sum of committee counts in range ``[start_slot, stop_slot)``. """ - return sum(get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(slot))) - for slot in range(start_slot, stop_slot)) + return sum( + get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(slot))) + for slot in range(start_slot, stop_slot) + ) ``` #### `get_start_shard` From db81f88c4ef1bfda8471f7e29f13f3e3c0236fea Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 18 Jun 2020 10:59:37 -0700 Subject: [PATCH 147/236] Update beacon-chain.md --- specs/phase1/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index ed01af6eb4..4542858fbb 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -901,7 +901,7 @@ def process_crosslink_for_shard(state: BeaconState, assert shard_transition_root == hash_tree_root(shard_transition) # Apply transition - apply_shard_transition(state, attestation.data.shard, shard_transition) + apply_shard_transition(state, attestations[0].data.shard, shard_transition) # Apply proposer reward and cost beacon_proposer_index = get_beacon_proposer_index(state) estimated_attester_reward = sum([get_base_reward(state, attester) for attester in transition_participants]) @@ -909,11 +909,11 @@ def process_crosslink_for_shard(state: BeaconState, increase_balance(state, beacon_proposer_index, proposer_reward) states_slots_lengths = zip( shard_transition.shard_states, - get_offset_slots(state, attestation.data.shard), + get_offset_slots(state, attestations[0].data.shard), shard_transition.shard_block_lengths ) for shard_state, slot, length in states_slots_lengths: - proposer_index = get_shard_proposer_index(state, slot, attestation.data.shard) + proposer_index = get_shard_proposer_index(state, slot, attestations[0].data.shard) decrease_balance(state, proposer_index, shard_state.gasprice * length) # Return winning transition root From 3888dc27a9fe936027da6a2a4f926b81459d7e0f Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 18 Jun 2020 12:39:48 -0700 Subject: [PATCH 148/236] @djrtwo's feedback --- specs/phase1/beacon-chain.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4542858fbb..a1a940adfe 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -876,6 +876,7 @@ def process_crosslink_for_shard(state: BeaconState, on_time_attestation_slot = compute_previous_slot(state.slot) committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) online_indices = get_online_validator_indices(state) + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) # Loop over all shard transition roots shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) @@ -901,7 +902,7 @@ def process_crosslink_for_shard(state: BeaconState, assert shard_transition_root == hash_tree_root(shard_transition) # Apply transition - apply_shard_transition(state, attestations[0].data.shard, shard_transition) + apply_shard_transition(state, shard, shard_transition) # Apply proposer reward and cost beacon_proposer_index = get_beacon_proposer_index(state) estimated_attester_reward = sum([get_base_reward(state, attester) for attester in transition_participants]) @@ -909,11 +910,11 @@ def process_crosslink_for_shard(state: BeaconState, increase_balance(state, beacon_proposer_index, proposer_reward) states_slots_lengths = zip( shard_transition.shard_states, - get_offset_slots(state, attestations[0].data.shard), + get_offset_slots(state, shard), shard_transition.shard_block_lengths ) for shard_state, slot, length in states_slots_lengths: - proposer_index = get_shard_proposer_index(state, slot, attestations[0].data.shard) + proposer_index = get_shard_proposer_index(state, slot, shard) decrease_balance(state, proposer_index, shard_state.gasprice * length) # Return winning transition root From ea591931570a67f27647651635d8d291105026e9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 18:11:28 +0800 Subject: [PATCH 149/236] Rename some `shard_blocks` to `shard_block_dict` --- .../eth2spec/test/fork_choice/test_on_shard_head.py | 2 +- .../core/pyspec/eth2spec/test/helpers/shard_block.py | 10 +++++++--- .../test_process_shard_transition.py | 4 ++-- .../eth2spec/test/phase1/sanity/test_blocks.py | 12 ++++++------ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index c84f073b24..f7b888a2e3 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -72,7 +72,7 @@ def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer) shard_transitions = get_shard_transitions( spec, state, - shard_blocks={shard: shard_blocks_buffer}, + shard_block_dict={shard: shard_blocks_buffer}, ) shard_transition = shard_transitions[shard] attestation = get_valid_on_time_attestation( diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 6957f22831..1193f05e54 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -30,7 +30,7 @@ def build_shard_block(spec, slot = shard_parent_state.slot + 1 if body is None: - body = b'\x56' * 128 + body = get_sample_shard_block_body() beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot) proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) @@ -52,10 +52,10 @@ def build_shard_block(spec, return signed_block -def get_shard_transitions(spec, parent_beacon_state, shard_blocks): +def get_shard_transitions(spec, parent_beacon_state, shard_block_dict): shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS on_time_slot = parent_beacon_state.slot + 1 - for shard, blocks in shard_blocks.items(): + for shard, blocks in shard_block_dict.items(): shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks) offset_slots = spec.compute_offset_slots( spec.get_latest_slot_for_shard(parent_beacon_state, shard), @@ -81,3 +81,7 @@ def get_committee_index_of_shard(spec, state, slot, shard): # Optional[Committe if (start_shard + committee_index) % active_shard_count == shard: return committee_index return None + + +def get_sample_shard_block_body(): + return b'\x56' * 128 diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index 6d88781772..0c9293b74a 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -16,7 +16,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): state = transition_to_valid_shard_slot(spec, state) committee_index = spec.CommitteeIndex(0) init_slot = state.slot - shard_slot = state.slot + target_len_offset_slot - 1 + shard_slot = init_slot + target_len_offset_slot - 1 shard = spec.compute_shard_from_committee_index(state, committee_index, shard_slot) assert state.shard_states[shard].slot == init_slot - 1 @@ -31,7 +31,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): shard_transitions = get_shard_transitions( spec, state, - shard_blocks={shard: shard_blocks}, + shard_block_dict={shard: shard_blocks}, ) shard_transition = shard_transitions[shard] attestation = get_valid_on_time_attestation( diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 33b0beac7b..aa24bfc1ea 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -19,9 +19,9 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) - shard_blocks: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} + shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} - shard_transitions = get_shard_transitions(spec, state, shard_blocks) + shard_transitions = get_shard_transitions(spec, state, shard_block_dict) attestations = [ get_valid_on_time_attestation( spec, @@ -30,7 +30,7 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm shard_transition=shard_transitions[shard], signed=True, ) - for shard in shard_blocks.keys() + for shard in shard_block_dict.keys() ] beacon_block = build_empty_block(spec, state, slot=state.slot + 1) @@ -50,16 +50,16 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm for shard in range(spec.get_active_shard_count(state)): post_shard_state = state.shard_states[shard] - if shard in shard_blocks: + if shard in shard_block_dict: # Shard state has been changed to state_transition result assert post_shard_state == shard_transitions[shard].shard_states[ len(shard_transitions[shard].shard_states) - 1 ] assert post_shard_state.slot == state.slot - 1 - if len(shard_blocks[shard]) == 0: + if len((shard_block_dict[shard])) == 0: # `latest_block_root` is the same assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root - if target_len_offset_slot == 1 and len(shard_blocks) > 0: + if target_len_offset_slot == 1 and len(shard_block_dict[shard]) > 0: assert post_shard_state.gasprice > pre_gasprice From 3117cf3140c400c706e2f5de082b5c13771dc58a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 19:09:11 +0800 Subject: [PATCH 150/236] Refactor --- .../eth2spec/test/helpers/shard_block.py | 7 +- .../test_process_shard_transition.py | 72 ++++++++++++------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 1193f05e54..c8d60938b7 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -30,7 +30,7 @@ def build_shard_block(spec, slot = shard_parent_state.slot + 1 if body is None: - body = get_sample_shard_block_body() + body = get_sample_shard_block_body(spec) beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot) proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) @@ -83,5 +83,6 @@ def get_committee_index_of_shard(spec, state, slot, shard): # Optional[Committe return None -def get_sample_shard_block_body(): - return b'\x56' * 128 +def get_sample_shard_block_body(spec, is_max=False): + size = spec.MAX_SHARD_BLOCK_SIZE if is_max else 128 + return b'\x56' * size diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index 0c9293b74a..afc4828f88 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -8,50 +8,63 @@ from eth2spec.test.helpers.shard_block import ( build_shard_block, get_shard_transitions, + get_sample_shard_block_body, + get_committee_index_of_shard, ) from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_slot -def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): +def get_initial_env(spec, state, target_len_offset_slot): state = transition_to_valid_shard_slot(spec, state) committee_index = spec.CommitteeIndex(0) + target_shard_slot = state.slot + target_len_offset_slot - 1 + shard = spec.compute_shard_from_committee_index(state, committee_index, target_shard_slot) + return state, shard, target_shard_slot + + +def get_attestations_and_shard_transitions(spec, state, shard_block_dict): + shard_transitions = get_shard_transitions(spec, state, shard_block_dict) + attestations = [ + get_valid_on_time_attestation( + spec, state, + index=get_committee_index_of_shard(spec, state, state.slot, shard), + shard_transition=shard_transition, + signed=False, + ) + for shard, shard_transition in enumerate(shard_transitions) + if shard_transition != spec.ShardTransition() + ] + return attestations, shard_transitions + + +def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): + state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot) init_slot = state.slot - shard_slot = init_slot + target_len_offset_slot - 1 - shard = spec.compute_shard_from_committee_index(state, committee_index, shard_slot) assert state.shard_states[shard].slot == init_slot - 1 - # Create SignedShardBlock - body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE - shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) - shard_blocks = [shard_block] - - # Transition state latest shard slot - transition_to(spec, state, shard_slot) - # Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot` - shard_transitions = get_shard_transitions( - spec, - state, - shard_block_dict={shard: shard_blocks}, - ) - shard_transition = shard_transitions[shard] - attestation = get_valid_on_time_attestation( - spec, - state, - index=committee_index, - shard_transition=shard_transition, - signed=False, + # Create SignedShardBlock at init_slot + shard_block = build_shard_block( + spec, state, shard, + slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True ) + + # Transition state to target shard slot + transition_to(spec, state, target_shard_slot) + + # Create a shard_transitions that would be included at beacon block `target_shard_slot + 1` + shard_block_dict = {shard: [shard_block]} + attestations, shard_transitions = get_attestations_and_shard_transitions(spec, state, shard_block_dict) + next_slot(spec, state) pre_gasprice = state.shard_states[shard].gasprice - transition_to(spec, state, init_slot + target_len_offset_slot) pre_shard_state = state.shard_states[shard] - yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid) + yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=valid) if valid: shard_state = state.shard_states[shard] assert shard_state != pre_shard_state - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + assert shard_state == shard_transitions[shard].shard_states[len(shard_transitions[shard].shard_states) - 1] assert shard_state.latest_block_root == shard_block.message.hash_tree_root() if target_len_offset_slot == 1: assert shard_state.gasprice > pre_gasprice @@ -69,3 +82,10 @@ def test_basic_crosslinks(spec, state): def test_multiple_offset_slots(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=2, valid=True) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_no_winning_root(spec, state): + # NOTE: this test is only for full crosslink (minimal config), not for mainnet + yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True) From c2c2b4c4441590da1e7c2286176d883f1578638a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 22:19:21 +0800 Subject: [PATCH 151/236] Crosslink bugfix 1. Fix `is_winning_attestation` condition 2. Fix `process_crosslink_for_shard`: we can only check `shard_data_roots` if `shard_transition != ShardTransition()` --- specs/phase1/beacon-chain.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index a1a940adfe..aa250f9c4a 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -671,7 +671,7 @@ def is_winning_attestation(state: BeaconState, ``winning_root`` formed by ``committee_index`` committee at the current slot. """ return ( - attestation.data.slot == state.slot + is_on_time_attestation(state, attestation) and attestation.data.index == committee_index and attestation.data.shard_transition_root == winning_root ) @@ -886,9 +886,10 @@ def process_crosslink_for_shard(state: BeaconState, for attestation in transition_attestations: participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) transition_participants = transition_participants.union(participants) - assert attestation.data.shard_head_root == shard_transition.shard_data_roots[ - len(shard_transition.shard_data_roots) - 1 - ] + if len(shard_transition.shard_data_roots) > 0: + # Is the `shard_transition` candidate + last_offset_index = len(shard_transition.shard_data_roots) - 1 + assert attestation.data.shard_head_root == shard_transition.shard_data_roots[last_offset_index] enough_online_stake = ( get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >= From 43605eebe8f24a5509c78cac47547e3fb5c518b1 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 22:21:29 +0800 Subject: [PATCH 152/236] Add `test_no_winning_root` --- .../test_process_shard_transition.py | 73 ++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index afc4828f88..ead5550988 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -3,7 +3,12 @@ with_all_phases_except, spec_state_test, ) -from eth2spec.test.helpers.attestations import get_valid_on_time_attestation +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, + get_valid_on_time_attestation, + run_attestation_processing, + sign_aggregate_attestation, +) from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing from eth2spec.test.helpers.shard_block import ( build_shard_block, @@ -19,6 +24,7 @@ def get_initial_env(spec, state, target_len_offset_slot): committee_index = spec.CommitteeIndex(0) target_shard_slot = state.slot + target_len_offset_slot - 1 shard = spec.compute_shard_from_committee_index(state, committee_index, target_shard_slot) + assert state.shard_states[shard].slot == state.slot - 1 return state, shard, target_shard_slot @@ -29,7 +35,7 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict): spec, state, index=get_committee_index_of_shard(spec, state, state.slot, shard), shard_transition=shard_transition, - signed=False, + signed=True, ) for shard, shard_transition in enumerate(shard_transitions) if shard_transition != spec.ShardTransition() @@ -37,10 +43,9 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict): return attestations, shard_transitions -def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): +def run_successful_crosslink_tests(spec, state, target_len_offset_slot, valid=True): state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot) init_slot = state.slot - assert state.shard_states[shard].slot == init_slot - 1 # Create SignedShardBlock at init_slot shard_block = build_shard_block( @@ -56,36 +61,86 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): attestations, shard_transitions = get_attestations_and_shard_transitions(spec, state, shard_block_dict) next_slot(spec, state) - pre_gasprice = state.shard_states[shard].gasprice + for attestation in attestations: + _, _, _ = run_attestation_processing(spec, state, attestation) + + pre_gasprice = state.shard_states[shard].gasprice pre_shard_state = state.shard_states[shard] yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=valid) if valid: shard_state = state.shard_states[shard] + shard_transition = shard_transitions[shard] assert shard_state != pre_shard_state - assert shard_state == shard_transitions[shard].shard_states[len(shard_transitions[shard].shard_states) - 1] + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] assert shard_state.latest_block_root == shard_block.message.hash_tree_root() if target_len_offset_slot == 1: assert shard_state.gasprice > pre_gasprice + for pending_attestation in state.current_epoch_attestations: + assert bool(pending_attestation.crosslink_success) is True + @with_all_phases_except([PHASE0]) @spec_state_test def test_basic_crosslinks(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet - yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True) + yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1) @with_all_phases_except([PHASE0]) @spec_state_test def test_multiple_offset_slots(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet - yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=2, valid=True) + yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2) @with_all_phases_except([PHASE0]) @spec_state_test def test_no_winning_root(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet - yield from run_basic_crosslink_tests(spec, state, target_len_offset_slot=1, valid=True) + state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1) + init_slot = state.slot + + # Create SignedShardBlock at init_slot + shard_block = build_shard_block( + spec, state, shard, + slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True + ) + + # Transition state to target shard slot + transition_to(spec, state, target_shard_slot) + + # Create a shard_transitions that would be included at beacon block `target_shard_slot + 1` + shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]}) + shard_transition = shard_transitions[shard] + committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) + attestation = get_valid_attestation( + spec, state, + index=committee_index, + shard_transition=shard_transition, + signed=True, + on_time=True, + ) + + # Decrease attested participants to 1/3 committee + beacon_committee = spec.get_beacon_committee(state, state.slot, committee_index) + attested_participants = beacon_committee[:len(beacon_committee) // 3] + for i in range(len(beacon_committee)): + attestation.aggregation_bits[i] = beacon_committee[i] in attested_participants + attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, attested_participants) + + next_slot(spec, state) + + _, _, _ = run_attestation_processing(spec, state, attestation) + + # No winning root, shard_transitions[shard] is empty + shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS + pre_shard_state = state.shard_states[shard] + yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation]) + + for pending_attestation in state.current_epoch_attestations: + assert bool(pending_attestation.crosslink_success) is False + + assert state.shard_states[shard] == pre_shard_state From 37d1a0750db7f01c3781638f8a35f36a1a78dc3f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 22:29:49 +0800 Subject: [PATCH 153/236] Use `get_shard_winning_roots` to verify --- .../block_processing/test_process_shard_transition.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index ead5550988..0a0722b9c9 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -65,6 +65,10 @@ def run_successful_crosslink_tests(spec, state, target_len_offset_slot, valid=Tr for attestation in attestations: _, _, _ = run_attestation_processing(spec, state, attestation) + _, winning_roots = spec.get_shard_winning_roots(state, attestations) + assert len(winning_roots) == 1 + assert winning_roots[0] == shard_transitions[shard].hash_tree_root() + pre_gasprice = state.shard_states[shard].gasprice pre_shard_state = state.shard_states[shard] yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=valid) @@ -135,6 +139,9 @@ def test_no_winning_root(spec, state): _, _, _ = run_attestation_processing(spec, state, attestation) + _, winning_roots = spec.get_shard_winning_roots(state, [attestation]) + assert len(winning_roots) == 0 + # No winning root, shard_transitions[shard] is empty shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS pre_shard_state = state.shard_states[shard] From a9f8411a51ea55f1506ec538d47ac8d56fe5e74d Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 22:42:18 +0800 Subject: [PATCH 154/236] Verify all shard states --- .../test_process_shard_transition.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index 0a0722b9c9..8d89e3911a 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -67,20 +67,23 @@ def run_successful_crosslink_tests(spec, state, target_len_offset_slot, valid=Tr _, winning_roots = spec.get_shard_winning_roots(state, attestations) assert len(winning_roots) == 1 - assert winning_roots[0] == shard_transitions[shard].hash_tree_root() + shard_transition = shard_transitions[shard] + assert winning_roots[0] == shard_transition.hash_tree_root() pre_gasprice = state.shard_states[shard].gasprice - pre_shard_state = state.shard_states[shard] + pre_shard_states = state.shard_states.copy() yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=valid) if valid: - shard_state = state.shard_states[shard] - shard_transition = shard_transitions[shard] - assert shard_state != pre_shard_state - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] - assert shard_state.latest_block_root == shard_block.message.hash_tree_root() - if target_len_offset_slot == 1: - assert shard_state.gasprice > pre_gasprice + for index, shard_state in enumerate(state.shard_states): + if index == shard: + assert shard_state != pre_shard_states[index] + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + assert shard_state.latest_block_root == shard_block.message.hash_tree_root() + if target_len_offset_slot == 1: + assert shard_state.gasprice > pre_gasprice + else: + assert shard_state == pre_shard_states[index] for pending_attestation in state.current_epoch_attestations: assert bool(pending_attestation.crosslink_success) is True @@ -144,10 +147,10 @@ def test_no_winning_root(spec, state): # No winning root, shard_transitions[shard] is empty shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS - pre_shard_state = state.shard_states[shard] + pre_shard_states = state.shard_states.copy() yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation]) for pending_attestation in state.current_epoch_attestations: assert bool(pending_attestation.crosslink_success) is False - assert state.shard_states[shard] == pre_shard_state + assert state.shard_states == pre_shard_states From b8caa6ee85c53a717cd394479134961e01be359a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 22:52:38 +0800 Subject: [PATCH 155/236] Add `test_wrong_shard_transition_root` --- .../test_process_shard_transition.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index 8d89e3911a..ad8e7a6339 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -154,3 +154,46 @@ def test_no_winning_root(spec, state): assert bool(pending_attestation.crosslink_success) is False assert state.shard_states == pre_shard_states + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_wrong_shard_transition_root(spec, state): + state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1) + init_slot = state.slot + + # Create SignedShardBlock at init_slot + shard_block = build_shard_block( + spec, state, shard, + slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True + ) + + # Transition state to target shard slot + transition_to(spec, state, target_shard_slot) + + # Create a shard_transitions that would be included at beacon block `target_shard_slot + 1` + shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]}) + shard_transition = shard_transitions[shard] + wrong_shard_transition = shard_transition.copy() + wrong_shard_transition.shard_states[shard].gasprice = shard_transition.shard_states[shard].gasprice + 1 + committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) + attestation = get_valid_attestation( + spec, state, + index=committee_index, + shard_transition=wrong_shard_transition, + signed=True, + on_time=True, + ) + attestations = [attestation] + + next_slot(spec, state) + + run_attestation_processing(spec, state, attestation) + + # Check if winning root != shard_transition.hash_tree_root() + _, winning_roots = spec.get_shard_winning_roots(state, attestations) + assert len(winning_roots) == 1 + shard_transition = shard_transitions[shard] + assert winning_roots[0] != shard_transition.hash_tree_root() + + yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=False) From 80c6c0b5f1ff62b74d7fc6850a453494f00d4e69 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 23:07:39 +0800 Subject: [PATCH 156/236] valid==True for `run_successful_crosslink_tests` helper --- .../test_process_shard_transition.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index ad8e7a6339..caae1a1c79 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -43,7 +43,7 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict): return attestations, shard_transitions -def run_successful_crosslink_tests(spec, state, target_len_offset_slot, valid=True): +def run_successful_crosslink_tests(spec, state, target_len_offset_slot): state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot) init_slot = state.slot @@ -72,21 +72,20 @@ def run_successful_crosslink_tests(spec, state, target_len_offset_slot, valid=Tr pre_gasprice = state.shard_states[shard].gasprice pre_shard_states = state.shard_states.copy() - yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=valid) - - if valid: - for index, shard_state in enumerate(state.shard_states): - if index == shard: - assert shard_state != pre_shard_states[index] - assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] - assert shard_state.latest_block_root == shard_block.message.hash_tree_root() - if target_len_offset_slot == 1: - assert shard_state.gasprice > pre_gasprice - else: - assert shard_state == pre_shard_states[index] - - for pending_attestation in state.current_epoch_attestations: - assert bool(pending_attestation.crosslink_success) is True + yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations) + + for index, shard_state in enumerate(state.shard_states): + if index == shard: + assert shard_state != pre_shard_states[index] + assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] + assert shard_state.latest_block_root == shard_block.message.hash_tree_root() + if target_len_offset_slot == 1: + assert shard_state.gasprice > pre_gasprice + else: + assert shard_state == pre_shard_states[index] + + for pending_attestation in state.current_epoch_attestations: + assert bool(pending_attestation.crosslink_success) is True @with_all_phases_except([PHASE0]) From c28857e4e1f7226cb177f60c94364398428d5b77 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 23:34:51 +0800 Subject: [PATCH 157/236] Fix `attestation.data.shard_head_root` bug --- specs/phase1/beacon-chain.md | 7 ++++--- tests/core/pyspec/eth2spec/test/helpers/attestations.py | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index aa250f9c4a..f90d61f267 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -886,10 +886,11 @@ def process_crosslink_for_shard(state: BeaconState, for attestation in transition_attestations: participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) transition_participants = transition_participants.union(participants) - if len(shard_transition.shard_data_roots) > 0: + if len(shard_transition.shard_states) > 0: # Is the `shard_transition` candidate - last_offset_index = len(shard_transition.shard_data_roots) - 1 - assert attestation.data.shard_head_root == shard_transition.shard_data_roots[last_offset_index] + last_offset_index = len(shard_transition.shard_states) - 1 + shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root + assert attestation.data.shard_head_root == shard_head_root enough_online_stake = ( get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >= diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 0c3f012f52..3445f9ff64 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -83,14 +83,14 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio attestation_data.shard = shard if shard_transition is not None: - lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 - attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] + last_offset_index = len(shard_transition.shard_data_roots) - 1 + attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: if on_time: shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[]) - lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 - attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] + last_offset_index = len(shard_transition.shard_data_roots) - 1 + attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: attestation_data.shard_head_root = state.shard_states[shard].latest_block_root From 1a5016157a24f059afcab33a309adc2f21e47543 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 19 Jun 2020 23:46:01 +0800 Subject: [PATCH 158/236] Fix 1. To make it more compatible, update `is_on_time_attestation` argument: replace `attestation: Attestation` with `attestation_data: AttestationData` 2. Fix `get_sample_shard_transition` --- specs/phase1/beacon-chain.md | 16 ++++++++-------- specs/phase1/validator.md | 2 +- .../core/pyspec/eth2spec/test/helpers/custody.py | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index f90d61f267..f9b123d3b0 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -652,11 +652,11 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: ```python def is_on_time_attestation(state: BeaconState, - attestation: Attestation) -> bool: + attestation_data: AttestationData) -> bool: """ - Check if the given attestation is on-time. + Check if the given ``attestation_data`` is on-time. """ - return attestation.data.slot == compute_previous_slot(state.slot) + return attestation_data.slot == compute_previous_slot(state.slot) ``` #### `is_winning_attestation` @@ -667,11 +667,11 @@ def is_winning_attestation(state: BeaconState, committee_index: CommitteeIndex, winning_root: Root) -> bool: """ - Check if ``attestation`` helped contribute to the successful crosslink of - ``winning_root`` formed by ``committee_index`` committee at the current slot. + Check if on-time ``attestation`` helped contribute to the successful crosslink of + ``winning_root`` formed by ``committee_index`` committee. """ return ( - is_on_time_attestation(state, attestation) + is_on_time_attestation(state, attestation.data) and attestation.data.index == committee_index and attestation.data.shard_transition_root == winning_root ) @@ -766,7 +766,7 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: assert attestation.data.source == state.previous_justified_checkpoint # Type 1: on-time attestations - if is_on_time_attestation(state, attestation): + if is_on_time_attestation(state, attestation.data): # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) # Correct shard number @@ -941,7 +941,7 @@ def process_crosslinks(state: BeaconState, # Since the attestations are validated, all `shard_attestations` satisfy `attestation.data.shard == shard` shard_attestations = [ attestation for attestation in attestations - if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index + if is_on_time_attestation(state, attestation.data) and attestation.data.index == committee_index ] winning_root = process_crosslink_for_shard( state, committee_index, shard_transitions[shard], shard_attestations diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index f347f8757b..fd3b7fef67 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -157,7 +157,7 @@ def get_shard_winning_roots(state: BeaconState, # All attestations in the block for this committee/shard and are "on time" shard_attestations = [ attestation for attestation in attestations - if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index + if is_on_time_attestation(state, attestation.data) and attestation.data.index == committee_index ] committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index f63a07099d..d25b91a410 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -172,7 +172,7 @@ def get_sample_shard_transition(spec, start_slot, block_lengths): start_slot=start_slot, shard_block_lengths=block_lengths, shard_data_roots=b, - shard_states=[spec.Root() for x in block_lengths], + shard_states=[spec.ShardState() for x in block_lengths], proposer_signature_aggregate=spec.BLSSignature(), ) return shard_transition From 769e1d0339479bf6cdab9f8fbdd2a49001928bd6 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Fri, 19 Jun 2020 09:06:37 -0700 Subject: [PATCH 159/236] Update shard-transition.md --- specs/phase1/shard-transition.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 50694ce682..bb3e76a4fa 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -42,8 +42,6 @@ def verify_shard_block_message(beacon_parent_state: BeaconState, next_slot = Slot(block.slot + 1) offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_parent_state, shard), next_slot) assert block.slot in offset_slots - # Check `shard` field - assert block.shard == shard # Check `proposer_index` field assert block.proposer_index == get_shard_proposer_index(beacon_parent_state, block.slot, shard) # Check `body` field From 40b397f53f3643b7ae9ad26859b0e9286127a269 Mon Sep 17 00:00:00 2001 From: ericsson Date: Sat, 20 Jun 2020 00:09:59 +0300 Subject: [PATCH 160/236] use [[]] * num instead of just [] * num --- specs/phase1/phase1-fork.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index 496191e073..6e466b16d1 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -111,7 +111,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: current_light_committee=CompactCommittee(), # computed after state creation next_light_committee=CompactCommittee(), # Custody game - exposed_derived_secrets=[] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, + exposed_derived_secrets=[[]] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, # exposed_derived_secrets will fully default to zeroes ) next_epoch = Epoch(epoch + 1) From 723784b40813aa5478e23cffc8925eaa7a1c06e4 Mon Sep 17 00:00:00 2001 From: ericsson Date: Sat, 20 Jun 2020 01:02:19 +0300 Subject: [PATCH 161/236] replace `[[]]*num` with `[()]*num` to avoild problems with mutability, as [] or () is copied --- specs/phase1/phase1-fork.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index 6e466b16d1..e83e9ef4a3 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -111,7 +111,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: current_light_committee=CompactCommittee(), # computed after state creation next_light_committee=CompactCommittee(), # Custody game - exposed_derived_secrets=[[]] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, + exposed_derived_secrets=[()] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, # exposed_derived_secrets will fully default to zeroes ) next_epoch = Epoch(epoch + 1) From 4f618fc62d4aefb1418f1834710669e80bd79105 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 22 Jun 2020 23:11:21 +0800 Subject: [PATCH 162/236] Rework `shard_state_transition` interface To make `shard_state_transition` similar to phase 0 `state_transition` function 1. Rename old `shard_state_transition` to `process_shard_block` 2. Add `shard_state_transition` with `validate_message` flag, we only validate it in shard fork choice --- specs/phase1/shard-fork-choice.md | 8 +++----- specs/phase1/shard-transition.md | 33 ++++++++++++++----------------- specs/phase1/validator.md | 4 ++-- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 411ad9b6b7..1a449e3e9e 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -169,14 +169,12 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si ) # Check the block is valid and compute the post-state - assert verify_shard_block_message(beacon_parent_state, shard_parent_state, shard_block) - assert verify_shard_block_signature(beacon_parent_state, signed_shard_block) - - post_state = get_post_shard_state(shard_parent_state, shard_block) + shard_state = shard_parent_state.copy() + shard_state_transition(beacon_parent_state, shard_state, shard_block) # Add new block to the store shard_store.blocks[hash_tree_root(shard_block)] = shard_block # Add new state for this block to the store - shard_store.block_states[hash_tree_root(shard_block)] = post_state + shard_store.block_states[hash_tree_root(shard_block)] = shard_state ``` diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 50694ce682..1433b0837b 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -42,8 +42,6 @@ def verify_shard_block_message(beacon_parent_state: BeaconState, next_slot = Slot(block.slot + 1) offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_parent_state, shard), next_slot) assert block.slot in offset_slots - # Check `shard` field - assert block.shard == shard # Check `proposer_index` field assert block.proposer_index == get_shard_proposer_index(beacon_parent_state, block.slot, shard) # Check `body` field @@ -63,8 +61,20 @@ def verify_shard_block_signature(beacon_state: BeaconState, ## Shard state transition ```python -def shard_state_transition(shard_state: ShardState, - block: ShardBlock) -> None: +def shard_state_transition(beacon_parent_state: BeaconState, + shard_state: ShardState, + block: ShardBlock, + validate_message: bool = True) -> bool: + if validate_message: + assert verify_shard_block_message(beacon_parent_state, shard_state, block) + + process_shard_block(shard_state, block) + return shard_state +``` + +```python +def process_shard_block(shard_state: ShardState, + block: ShardBlock) -> None: """ Update ``shard_state`` with shard ``block``. """ @@ -79,19 +89,6 @@ def shard_state_transition(shard_state: ShardState, shard_state.latest_block_root = latest_block_root ``` -We have a pure function `get_post_shard_state` for describing the fraud proof verification and honest validator behavior. - -```python -def get_post_shard_state(shard_state: ShardState, - block: ShardBlock) -> ShardState: - """ - A pure function that returns a new post ShardState instead of modifying the given `shard_state`. - """ - post_state = shard_state.copy() - shard_state_transition(post_state, block) - return post_state -``` - ## Fraud proofs ### Verifying the proof @@ -129,7 +126,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - shard_state = get_post_shard_state(shard_state, block) + shard_state_transition(beacon_state, shard_state, block, validate_message=False) if shard_state != transition.shard_states[offset_index]: return True diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index f347f8757b..39ada78913 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -286,7 +286,7 @@ def get_shard_transition_fields( shard_data_roots = [] shard_block_lengths = [] - shard_state = beacon_state.shard_states[shard] + shard_state = beacon_state.shard_states[shard].copy() shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks] offset_slots = compute_offset_slots( get_latest_slot_for_shard(beacon_state, shard), @@ -299,7 +299,7 @@ def get_shard_transition_fields( else: shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) - shard_state = get_post_shard_state(shard_state, shard_block.message) + shard_state_transition(beacon_state, shard_state, shard_block.message, validate_message=False) shard_states.append(shard_state) shard_block_lengths.append(len(shard_block.message.body)) From eec14424170f1afd6a3220d534dac3aa26dfebde Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 22 Jun 2020 23:41:02 +0800 Subject: [PATCH 163/236] Reorg `shard_state_transition` argument and fix `get_shard_transition_fields` --- specs/phase1/shard-fork-choice.md | 2 +- specs/phase1/shard-transition.md | 9 +++++---- specs/phase1/validator.md | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 1a449e3e9e..b6d5c5ad41 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -170,7 +170,7 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si # Check the block is valid and compute the post-state shard_state = shard_parent_state.copy() - shard_state_transition(beacon_parent_state, shard_state, shard_block) + shard_state_transition(shard_state, shard_block, validate_message=True, beacon_parent_state=beacon_parent_state) # Add new block to the store shard_store.blocks[hash_tree_root(shard_block)] = shard_block diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 1433b0837b..d725cd8bf7 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -61,11 +61,12 @@ def verify_shard_block_signature(beacon_state: BeaconState, ## Shard state transition ```python -def shard_state_transition(beacon_parent_state: BeaconState, - shard_state: ShardState, +def shard_state_transition(shard_state: ShardState, block: ShardBlock, - validate_message: bool = True) -> bool: + validate_message: bool = True, + beacon_parent_state: Optional[BeaconState] = None) -> bool: if validate_message: + assert beacon_parent_state is not None assert verify_shard_block_message(beacon_parent_state, shard_state, block) process_shard_block(shard_state, block) @@ -126,7 +127,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - shard_state_transition(beacon_state, shard_state, block, validate_message=False) + shard_state_transition(shard_state, block, validate_message=False) if shard_state != transition.shard_states[offset_index]: return True diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 39ada78913..d78726bc29 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -280,13 +280,12 @@ def get_shard_transition_fields( beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock], - validate_signature: bool=True, ) -> Tuple[Sequence[uint64], Sequence[Root], Sequence[ShardState]]: shard_states = [] shard_data_roots = [] shard_block_lengths = [] - shard_state = beacon_state.shard_states[shard].copy() + shard_state = beacon_state.shard_states[shard] shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks] offset_slots = compute_offset_slots( get_latest_slot_for_shard(beacon_state, shard), @@ -299,7 +298,8 @@ def get_shard_transition_fields( else: shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) - shard_state_transition(beacon_state, shard_state, shard_block.message, validate_message=False) + shard_state = shard_state.copy() + shard_state_transition(shard_state, shard_block.message, validate_message=False) shard_states.append(shard_state) shard_block_lengths.append(len(shard_block.message.body)) From a1e3392d2f0b62209577174a79e87c09da9149b2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 22 Jun 2020 09:47:22 -0600 Subject: [PATCH 164/236] add tests for on_attestation ceckpont state fix --- .../test/fork_choice/test_on_attestation.py | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py index a5334c5c78..d8885a0147 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py @@ -163,9 +163,9 @@ def test_on_attestation_inconsistent_target_and_head(spec, state): @with_all_phases @spec_state_test -def test_on_attestation_target_not_in_store(spec, state): +def test_on_attestation_target_block_not_in_store(spec, state): store = spec.get_forkchoice_store(state) - time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1) spec.on_tick(store, time) # move to immediately before next epoch to make block new target @@ -183,11 +183,63 @@ def test_on_attestation_target_not_in_store(spec, state): run_on_attestation(spec, state, store, attestation, False) +@with_all_phases +@spec_state_test +def test_on_attestation_target_checkpoint_not_in_store(spec, state): + store = spec.get_forkchoice_store(state) + time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1) + spec.on_tick(store, time) + + # move to immediately before next epoch to make block new target + next_epoch = spec.get_current_epoch(state) + 1 + transition_to(spec, state, spec.compute_start_slot_at_epoch(next_epoch) - 1) + + target_block = build_empty_block_for_next_slot(spec, state) + signed_target_block = state_transition_and_sign_block(spec, state, target_block) + + # add target block to store + spec.on_block(store, signed_target_block) + + # target checkpoint state is not yet in store + + attestation = get_valid_attestation(spec, state, slot=target_block.slot, signed=True) + assert attestation.data.target.root == target_block.hash_tree_root() + + run_on_attestation(spec, state, store, attestation) + + +@with_all_phases +@spec_state_test +def test_on_attestation_target_checkpoint_not_in_store_diff_slot(spec, state): + store = spec.get_forkchoice_store(state) + time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1) + spec.on_tick(store, time) + + # move to two slots before next epoch to make target block one before an empty slot + next_epoch = spec.get_current_epoch(state) + 1 + transition_to(spec, state, spec.compute_start_slot_at_epoch(next_epoch) - 2) + + target_block = build_empty_block_for_next_slot(spec, state) + signed_target_block = state_transition_and_sign_block(spec, state, target_block) + + # add target block to store + spec.on_block(store, signed_target_block) + + # target checkpoint state is not yet in store + + attestation_slot = target_block.slot + 1 + transition_to(spec, state, attestation_slot) + attestation = get_valid_attestation(spec, state, slot=attestation_slot, signed=True) + assert attestation.data.target.root == target_block.hash_tree_root() + + run_on_attestation(spec, state, store, attestation) + + @with_all_phases @spec_state_test def test_on_attestation_beacon_block_not_in_store(spec, state): store = spec.get_forkchoice_store(state) - time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH + time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1) spec.on_tick(store, time) # move to immediately before next epoch to make block new target From 97d0048eaa86cebcd2d4f89c511753bde474af2a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 23 Jun 2020 00:06:35 +0800 Subject: [PATCH 165/236] PR feedback: use condition to determine if the test should be skipped --- .../test_process_shard_transition.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index caae1a1c79..0f610c161c 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -43,6 +43,11 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict): return attestations, shard_transitions +def is_full_crosslink(spec, state): + epoch = spec.compute_epoch_at_slot(state.slot) + return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state) + + def run_successful_crosslink_tests(spec, state, target_len_offset_slot): state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot) init_slot = state.slot @@ -91,21 +96,30 @@ def run_successful_crosslink_tests(spec, state, target_len_offset_slot): @with_all_phases_except([PHASE0]) @spec_state_test def test_basic_crosslinks(spec, state): - # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # Skip this test + return + yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1) @with_all_phases_except([PHASE0]) @spec_state_test def test_multiple_offset_slots(spec, state): - # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # Skip this test + return + yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2) @with_all_phases_except([PHASE0]) @spec_state_test def test_no_winning_root(spec, state): - # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # Skip this test + return + state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1) init_slot = state.slot @@ -158,6 +172,10 @@ def test_no_winning_root(spec, state): @with_all_phases_except([PHASE0]) @spec_state_test def test_wrong_shard_transition_root(spec, state): + if not is_full_crosslink(spec, state): + # Skip this test + return + state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1) init_slot = state.slot From 5357bddcf8e49ff8d3a13938f71aa8fb14e5691c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 23 Jun 2020 00:13:48 +0800 Subject: [PATCH 166/236] PR feedback: use `filter_participant_set` --- .../block_processing/test_process_shard_transition.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index 0f610c161c..b14b836c0b 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -7,7 +7,6 @@ get_valid_attestation, get_valid_on_time_attestation, run_attestation_processing, - sign_aggregate_attestation, ) from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing from eth2spec.test.helpers.shard_block import ( @@ -140,17 +139,12 @@ def test_no_winning_root(spec, state): spec, state, index=committee_index, shard_transition=shard_transition, + # Decrease attested participants to 1/3 committee + filter_participant_set=lambda committee: set(list(committee)[:len(committee) // 3]), signed=True, on_time=True, ) - # Decrease attested participants to 1/3 committee - beacon_committee = spec.get_beacon_committee(state, state.slot, committee_index) - attested_participants = beacon_committee[:len(beacon_committee) // 3] - for i in range(len(beacon_committee)): - attestation.aggregation_bits[i] = beacon_committee[i] in attested_participants - attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, attested_participants) - next_slot(spec, state) _, _, _ = run_attestation_processing(spec, state, attestation) From ddddc4ba9958833aad4e2724c33a94de5c49d93c Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 23 Jun 2020 02:06:27 +0200 Subject: [PATCH 167/236] attester slashing with 0 indices and out of bounds indices --- .../test_process_attester_slashing.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 0635144988..724e5bf6fb 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -197,6 +197,74 @@ def test_participants_already_slashed(spec, state): # Some of the following tests are phase0 only: phase 1 lists participants with bitfields instead of index list. +@with_phases([PHASE0]) +@spec_state_test +@always_bls +def test_att1_high_index(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + + indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) + indices.append(spec.ValidatorIndex(len(state.validators))) # off by 1 + attester_slashing.attestation_1.attesting_indices = indices + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +@with_phases([PHASE0]) +@spec_state_test +@always_bls +def test_att2_high_index(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + + indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_2) + indices.append(spec.ValidatorIndex(len(state.validators))) # off by 1 + attester_slashing.attestation_2.attesting_indices = indices + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +EMPTY_SIGNATURE = b'\xc0' + (b'\x00' * 95) + + +@with_phases([PHASE0]) +@spec_state_test +@always_bls +def test_att1_empty_indices(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) + + attester_slashing.attestation_1.attesting_indices = [] + attester_slashing.attestation_1.signature = EMPTY_SIGNATURE + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +@with_phases([PHASE0]) +@spec_state_test +@always_bls +def test_att2_empty_indices(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) + + attester_slashing.attestation_2.attesting_indices = [] + attester_slashing.attestation_2.signature = EMPTY_SIGNATURE + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + +@with_phases([PHASE0]) +@spec_state_test +@always_bls +def test_all_empty_indices(spec, state): + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False) + + attester_slashing.attestation_1.attesting_indices = [] + attester_slashing.attestation_1.signature = EMPTY_SIGNATURE + + attester_slashing.attestation_2.attesting_indices = [] + attester_slashing.attestation_2.signature = EMPTY_SIGNATURE + + yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + + @with_phases([PHASE0]) @spec_state_test @always_bls From db5da9dc973c0fac9e1b9d1d3e19f755b91a8ac8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 23 Jun 2020 10:50:41 +0800 Subject: [PATCH 168/236] Fix return type Co-authored-by: Danny Ryan --- specs/phase1/shard-transition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index d725cd8bf7..e9982b3701 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -64,7 +64,7 @@ def verify_shard_block_signature(beacon_state: BeaconState, def shard_state_transition(shard_state: ShardState, block: ShardBlock, validate_message: bool = True, - beacon_parent_state: Optional[BeaconState] = None) -> bool: + beacon_parent_state: Optional[BeaconState] = None) -> ShardState: if validate_message: assert beacon_parent_state is not None assert verify_shard_block_message(beacon_parent_state, shard_state, block) From e52c19896da33751ad85cbfed3315c6f4f607f04 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 23 Jun 2020 18:22:18 +0800 Subject: [PATCH 169/236] Update 1. Check on-time attestations fit `attestation.data.shard_transition_root != hash_tree_root(ShardTransition())` 2. Move `attestation.data.shard_head_root` check to after winning root --- specs/phase1/beacon-chain.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index f9b123d3b0..5a5200c9e8 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -772,6 +772,8 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: # Correct shard number shard = compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) assert attestation.data.shard == shard + # On-time attestations should have a non-empty shard transition root + assert attestation.data.shard_transition_root != hash_tree_root(ShardTransition()) # Type 2: no shard transition else: # Ensure delayed attestation @@ -886,11 +888,6 @@ def process_crosslink_for_shard(state: BeaconState, for attestation in transition_attestations: participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) transition_participants = transition_participants.union(participants) - if len(shard_transition.shard_states) > 0: - # Is the `shard_transition` candidate - last_offset_index = len(shard_transition.shard_states) - 1 - shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root - assert attestation.data.shard_head_root == shard_head_root enough_online_stake = ( get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >= @@ -903,6 +900,12 @@ def process_crosslink_for_shard(state: BeaconState, # Attestation <-> shard transition consistency assert shard_transition_root == hash_tree_root(shard_transition) + # Check `shard_head_root` of the winning root + last_offset_index = len(shard_transition.shard_states) - 1 + shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root + for attestation in transition_attestations: + assert attestation.data.shard_head_root == shard_head_root + # Apply transition apply_shard_transition(state, shard, shard_transition) # Apply proposer reward and cost From 4bf10be4ffd8a8b344240918fee69b4c21507338 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 23 Jun 2020 21:14:43 +0200 Subject: [PATCH 170/236] use BLS constant for special signature --- .../test_process_attester_slashing.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 724e5bf6fb..192e0390d1 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -223,9 +223,6 @@ def test_att2_high_index(spec, state): yield from run_attester_slashing_processing(spec, state, attester_slashing, False) -EMPTY_SIGNATURE = b'\xc0' + (b'\x00' * 95) - - @with_phases([PHASE0]) @spec_state_test @always_bls @@ -233,7 +230,7 @@ def test_att1_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) attester_slashing.attestation_1.attesting_indices = [] - attester_slashing.attestation_1.signature = EMPTY_SIGNATURE + attester_slashing.attestation_1.signature = spec.bls.Z2_SIGNATURE yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -245,7 +242,7 @@ def test_att2_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) attester_slashing.attestation_2.attesting_indices = [] - attester_slashing.attestation_2.signature = EMPTY_SIGNATURE + attester_slashing.attestation_2.signature = spec.bls.Z2_SIGNATURE yield from run_attester_slashing_processing(spec, state, attester_slashing, False) @@ -257,10 +254,10 @@ def test_all_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False) attester_slashing.attestation_1.attesting_indices = [] - attester_slashing.attestation_1.signature = EMPTY_SIGNATURE + attester_slashing.attestation_1.signature = spec.bls.Z2_SIGNATURE attester_slashing.attestation_2.attesting_indices = [] - attester_slashing.attestation_2.signature = EMPTY_SIGNATURE + attester_slashing.attestation_2.signature = spec.bls.Z2_SIGNATURE yield from run_attester_slashing_processing(spec, state, attester_slashing, False) From dbd1d4e5896926109dc8244b651fbd37bab2b3e6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 24 Jun 2020 12:14:22 +0800 Subject: [PATCH 171/236] PR feedback: Enable `verify_shard_block_signature` --- specs/phase1/shard-fork-choice.md | 4 +++- specs/phase1/shard-transition.md | 19 ++++++++++--------- specs/phase1/validator.md | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index b6d5c5ad41..8d64eb842e 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -170,7 +170,9 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si # Check the block is valid and compute the post-state shard_state = shard_parent_state.copy() - shard_state_transition(shard_state, shard_block, validate_message=True, beacon_parent_state=beacon_parent_state) + shard_state_transition( + shard_state, signed_shard_block, + validate=True, beacon_parent_state=beacon_parent_state) # Add new block to the store shard_store.blocks[hash_tree_root(shard_block)] = shard_block diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index e9982b3701..05f9860000 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -50,10 +50,10 @@ def verify_shard_block_message(beacon_parent_state: BeaconState, ``` ```python -def verify_shard_block_signature(beacon_state: BeaconState, +def verify_shard_block_signature(beacon_parent_state: BeaconState, signed_block: SignedShardBlock) -> bool: - proposer = beacon_state.validators[signed_block.message.proposer_index] - domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot)) + proposer = beacon_parent_state.validators[signed_block.message.proposer_index] + domain = get_domain(beacon_parent_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot)) signing_root = compute_signing_root(signed_block.message, domain) return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` @@ -62,14 +62,15 @@ def verify_shard_block_signature(beacon_state: BeaconState, ```python def shard_state_transition(shard_state: ShardState, - block: ShardBlock, - validate_message: bool = True, + signed_block: SignedShardBlock, + validate: bool = True, beacon_parent_state: Optional[BeaconState] = None) -> ShardState: - if validate_message: + if validate: assert beacon_parent_state is not None - assert verify_shard_block_message(beacon_parent_state, shard_state, block) + assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message) + assert verify_shard_block_signature(beacon_parent_state, signed_block) - process_shard_block(shard_state, block) + process_shard_block(shard_state, signed_block.message) return shard_state ``` @@ -127,7 +128,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - shard_state_transition(shard_state, block, validate_message=False) + shard_state_transition(shard_state, block, validate=False) if shard_state != transition.shard_states[offset_index]: return True diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index d78726bc29..c0d6ecbf92 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -299,7 +299,7 @@ def get_shard_transition_fields( shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) shard_state = shard_state.copy() - shard_state_transition(shard_state, shard_block.message, validate_message=False) + shard_state_transition(shard_state, shard_block, validate=False) shard_states.append(shard_state) shard_block_lengths.append(len(shard_block.message.body)) From c4973792e2d7125b85d43c634f77d08e25dfc8d8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 24 Jun 2020 12:50:27 +0800 Subject: [PATCH 172/236] Make `get_pending_shard_blocks` return `Sequence[SignedShardBlock]` --- specs/phase1/shard-fork-choice.md | 33 +++++++++++-------- .../test/fork_choice/test_on_shard_head.py | 7 ++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 8d64eb842e..c34501a6df 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -36,7 +36,7 @@ This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase @dataclass class ShardStore: shard: Shard - blocks: Dict[Root, ShardBlock] = field(default_factory=dict) + signed_blocks: Dict[Root, SignedShardBlock] = field(default_factory=dict) block_states: Dict[Root, ShardState] = field(default_factory=dict) ``` @@ -46,7 +46,11 @@ class ShardStore: def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore: return ShardStore( shard=shard, - blocks={anchor_state.shard_states[shard].latest_block_root: ShardBlock(slot=anchor_state.slot, shard=shard)}, + signed_blocks={ + anchor_state.shard_states[shard].latest_block_root: SignedShardBlock( + message=ShardBlock(slot=anchor_state.slot, shard=shard) + ) + }, block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]}, ) ``` @@ -65,7 +69,7 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro # would be ignored once their newer vote is accepted. Check if it makes sense. and store.latest_messages[i].shard == shard_store.shard and get_shard_ancestor( - store, shard_store, store.latest_messages[i].shard_root, shard_store.blocks[root].slot + store, shard_store, store.latest_messages[i].shard_root, shard_store.signed_blocks[root].message.slot ) == root ) )) @@ -80,8 +84,8 @@ def get_shard_head(store: Store, shard_store: ShardStore) -> Root: shard_head_state = store.block_states[beacon_head_root].shard_states[shard_store.shard] shard_head_root = shard_head_state.latest_block_root shard_blocks = { - root: shard_block for root, shard_block in shard_store.blocks.items() - if shard_block.slot > shard_head_state.slot + root: signed_shard_block.message for root, signed_shard_block in shard_store.signed_blocks.items() + if signed_shard_block.message.slot > shard_head_state.slot } while True: # Find the valid child block roots @@ -101,7 +105,7 @@ def get_shard_head(store: Store, shard_store: ShardStore) -> Root: ```python def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: Slot) -> Root: - block = shard_store.blocks[root] + block = shard_store.signed_blocks[root].message if block.slot > slot: return get_shard_ancestor(store, shard_store, block.shard_parent_root, slot) elif block.slot == slot: @@ -114,7 +118,7 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: #### `get_pending_shard_blocks` ```python -def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ShardBlock]: +def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[SignedShardBlock]: """ Return the canonical shard block branch that has not yet been crosslinked. """ @@ -126,14 +130,14 @@ def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[ shard_head_root = get_shard_head(store, shard_store) root = shard_head_root - shard_blocks = [] + signed_shard_blocks = [] while root != latest_shard_block_root: - shard_block = shard_store.blocks[root] - shard_blocks.append(shard_block) - root = shard_block.shard_parent_root + signed_shard_block = shard_store.signed_blocks[root] + signed_shard_blocks.append(signed_shard_block) + root = signed_shard_block.message.shard_parent_root - shard_blocks.reverse() - return shard_blocks + signed_shard_blocks.reverse() + return signed_shard_blocks ``` ### Handlers @@ -175,7 +179,8 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si validate=True, beacon_parent_state=beacon_parent_state) # Add new block to the store - shard_store.blocks[hash_tree_root(shard_block)] = shard_block + # Note: storing `SignedShardBlock` format for computing `ShardTransition.proposer_signature_aggregate` + shard_store.signed_blocks[hash_tree_root(shard_block)] = signed_shard_block # Add new state for this block to the store shard_store.block_states[hash_tree_root(shard_block)] = shard_state diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py index f7b888a2e3..a87a859171 100644 --- a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py +++ b/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py @@ -22,7 +22,7 @@ def run_on_shard_block(spec, store, shard_store, signed_block, valid=True): assert False spec.on_shard_block(store, shard_store, signed_block) - assert shard_store.blocks[hash_tree_root(signed_block.message)] == signed_block.message + assert shard_store.signed_blocks[hash_tree_root(signed_block.message)] == signed_block def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_blocks_buffer): @@ -41,10 +41,7 @@ def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_block def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer): - pending_shard_blocks = [ - spec.SignedShardBlock(message=b) - for b in spec.get_pending_shard_blocks(store, shard_store) - ] + pending_shard_blocks = spec.get_pending_shard_blocks(store, shard_store) assert pending_shard_blocks == shard_blocks_buffer From 4a46fb2a8a91a3389bfe20b22e5c4d8ee7f28f28 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 24 Jun 2020 18:08:26 +0800 Subject: [PATCH 173/236] Refactor phase 1 block tests a bit and add `shard_state_transition` tests 1. Refacotr phase1/sanity/test_blocks.py 2. Add phase1/sanity/test_shard_blocks.py for testing `verify_shard_block_message` and `verify_shard_block_signature` --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/shard-transition.md | 12 +- .../test/helpers/shard_transitions.py | 5 + .../test_process_shard_transition.py | 10 +- .../test/phase1/sanity/test_blocks.py | 25 ++- .../test/phase1/sanity/test_shard_blocks.py | 151 ++++++++++++++++++ 6 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 5a5200c9e8..36abac85f8 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards +# Ethereum 2.0 Phase 1 -- The Beacon Chain with Shards **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 05f9860000..09e603c58d 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -11,7 +11,9 @@ - [Introduction](#introduction) - [Helper functions](#helper-functions) - [Shard block verification functions](#shard-block-verification-functions) -- [Shard state transition](#shard-state-transition) + - [`verify_shard_block_message`](#verify_shard_block_message) + - [`verify_shard_block_signature`](#verify_shard_block_signature) +- [Shard state transition function](#shard-state-transition-function) - [Fraud proofs](#fraud-proofs) - [Verifying the proof](#verifying-the-proof) @@ -25,6 +27,8 @@ This document describes the shard transition function and fraud proofs as part o ### Shard block verification functions +#### `verify_shard_block_message` + ```python def verify_shard_block_message(beacon_parent_state: BeaconState, shard_parent_state: ShardState, @@ -49,6 +53,8 @@ def verify_shard_block_message(beacon_parent_state: BeaconState, return True ``` +#### `verify_shard_block_signature` + ```python def verify_shard_block_signature(beacon_parent_state: BeaconState, signed_block: SignedShardBlock) -> bool: @@ -58,7 +64,9 @@ def verify_shard_block_signature(beacon_parent_state: BeaconState, return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` -## Shard state transition +## Shard state transition function + +The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_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. State transitions that cause a `uint64` overflow or underflow are also considered invalid. ```python def shard_state_transition(shard_state: ShardState, diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index d10d1ee7bc..6e508e3970 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -35,3 +35,8 @@ def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks) return shard_transition + + +def is_full_crosslink(spec, state): + epoch = spec.compute_epoch_at_slot(state.slot) + return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index b14b836c0b..8668316863 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -8,7 +8,10 @@ get_valid_on_time_attestation, run_attestation_processing, ) -from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing +from eth2spec.test.helpers.shard_transitions import ( + run_shard_transitions_processing, + is_full_crosslink, +) from eth2spec.test.helpers.shard_block import ( build_shard_block, get_shard_transitions, @@ -42,11 +45,6 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict): return attestations, shard_transitions -def is_full_crosslink(spec, state): - epoch = spec.compute_epoch_at_slot(state.slot) - return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state) - - def run_successful_crosslink_tests(spec, state, target_len_offset_slot): state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot) init_slot = state.slot diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index aa24bfc1ea..3cb2fe6c01 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -9,15 +9,17 @@ from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.shard_block import ( build_shard_block, + get_sample_shard_block_body, get_shard_transitions, ) +from eth2spec.test.helpers.shard_transitions import is_full_crosslink from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True): transition_to(spec, state, state.slot + target_len_offset_slot) - body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + body = get_sample_shard_block_body(spec, is_max=True) shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} @@ -40,13 +42,16 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm pre_gasprice = state.shard_states[shard].gasprice pre_shard_states = state.shard_states.copy() yield 'pre', state.copy() - yield 'block', beacon_block - state_transition_and_sign_block(spec, state, beacon_block) - if valid: - yield 'post', state - else: + + if not valid: + state_transition_and_sign_block(spec, state, beacon_block, expect_fail=True) + yield 'block', beacon_block yield 'post', None return + else: + state_transition_and_sign_block(spec, state, beacon_block) + yield 'block', beacon_block + yield 'post', None for shard in range(spec.get_active_shard_count(state)): post_shard_state = state.shard_states[shard] @@ -67,6 +72,10 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm @spec_state_test def test_process_beacon_block_with_normal_shard_transition(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # skip + return + state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 @@ -81,6 +90,10 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): @spec_state_test def test_process_beacon_block_with_empty_proposal_transition(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # skip + return + state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py new file mode 100644 index 0000000000..40d7e06505 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -0,0 +1,151 @@ +from eth2spec.test.context import ( + PHASE0, + always_bls, + expect_assertion_error, + spec_state_test, + with_all_phases_except, +) +from eth2spec.test.helpers.shard_block import ( + build_shard_block, + sign_shard_block, +) +from eth2spec.test.helpers.shard_transitions import is_full_crosslink +from eth2spec.test.helpers.state import transition_to_valid_shard_slot + + +def run_shard_blocks(spec, shard_state, signed_shard_block, + beacon_parent_state, + validate=True, valid=True): + yield 'pre', shard_state.copy() + yield 'signed_shard_block', signed_shard_block + yield 'validate', validate + yield 'beacon_parent_state', beacon_parent_state + + if validate is False: + beacon_parent_state = None + + if not valid: + expect_assertion_error(lambda: spec.shard_state_transition( + shard_state, signed_shard_block, validate=validate, beacon_parent_state=beacon_parent_state) + ) + yield 'post', None + else: + spec.shard_state_transition(shard_state, signed_shard_block, + validate=validate, beacon_parent_state=beacon_parent_state) + yield 'post', shard_state + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_valid_shard_block(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state) + + +# +# verify_shard_block_message +# + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_shard_parent_root(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + signed_shard_block.message.shard_parent_root = b'\x12' * 32 + sign_shard_block(spec, beacon_state, shard, signed_shard_block) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_beacon_parent_root(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + signed_shard_block.message.beacon_parent_root = b'\x12' * 32 + sign_shard_block(spec, beacon_state, shard, signed_shard_block) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_slot(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + signed_shard_block.message.slot = beacon_state.slot + 1 + proposer_index = spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_proposer_index(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + active_validator_indices = spec.get_active_validator_indices(beacon_state, spec.get_current_epoch(beacon_state)) + proposer_index = ( + (spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + 1) + % len(active_validator_indices) + ) + signed_shard_block.message.proposer_index = proposer_index + sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +# +# verify_shard_block_signature +# + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_invalid_signature(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) From 661959c13ec422cbfb588d40f0cb57fd9ff5c5fa Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 24 Jun 2020 18:18:50 +0800 Subject: [PATCH 174/236] Add `process_shard_block` verification --- specs/phase1/shard-transition.md | 7 ++---- .../test/phase1/sanity/test_shard_blocks.py | 22 +++++++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 09e603c58d..1ca5a9ab8a 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -92,11 +92,8 @@ def process_shard_block(shard_state: ShardState, prev_gasprice = shard_state.gasprice shard_block_length = len(block.body) shard_state.gasprice = compute_updated_gasprice(prev_gasprice, uint64(shard_block_length)) - if shard_block_length == 0: - latest_block_root = shard_state.latest_block_root - else: - latest_block_root = hash_tree_root(block) - shard_state.latest_block_root = latest_block_root + if shard_block_length != 0: + shard_state.latest_block_root = hash_tree_root(block) ``` ## Fraud proofs diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index 40d7e06505..4d07f340a6 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -16,7 +16,8 @@ def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, validate=True, valid=True): - yield 'pre', shard_state.copy() + pre_shard_state = shard_state.copy() + yield 'pre', pre_shard_state yield 'signed_shard_block', signed_shard_block yield 'validate', validate yield 'beacon_parent_state', beacon_parent_state @@ -29,10 +30,23 @@ def run_shard_blocks(spec, shard_state, signed_shard_block, shard_state, signed_shard_block, validate=validate, beacon_parent_state=beacon_parent_state) ) yield 'post', None + return + + spec.shard_state_transition(shard_state, signed_shard_block, + validate=validate, beacon_parent_state=beacon_parent_state) + yield 'post', shard_state + + # Verify `process_shard_block` + block = signed_shard_block.message + + assert shard_state.slot == block.slot + + shard_block_length = len(block.body) + assert shard_state.gasprice == spec.compute_updated_gasprice(pre_shard_state.gasprice, shard_block_length) + if shard_block_length != 0: + shard_state.latest_block_root == block.hash_tree_root() else: - spec.shard_state_transition(shard_state, signed_shard_block, - validate=validate, beacon_parent_state=beacon_parent_state) - yield 'post', shard_state + shard_state.latest_block_root == pre_shard_state.latest_block_root @with_all_phases_except([PHASE0]) From 593ed032f3e04153b747acff7d016fe1692d1100 Mon Sep 17 00:00:00 2001 From: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Date: Wed, 24 Jun 2020 17:09:58 +0200 Subject: [PATCH 175/236] SSZ Typescript Lodestar implementation repo moved --- ssz/simple-serialize.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 700a428e86..a9d1da56e3 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -251,7 +251,7 @@ We similarly define "summary types" and "expansion types". For example, [`Beacon | Rust | Lighthouse | Sigma Prime | [https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz](https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz) | | Nim | Nimbus | Status | [https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) | | Rust | Shasper | ParityTech | [https://github.com/paritytech/shasper/tree/master/utils/ssz](https://github.com/paritytech/shasper/tree/master/utils/ssz) | -| TypeScript | Lodestar | ChainSafe Systems | [https://github.com/ChainSafe/ssz-js](https://github.com/ChainSafe/ssz-js) | +| TypeScript | Lodestar | ChainSafe Systems | [https://github.com/ChainSafe/ssz-js](https://github.com/ChainSafe/ssz) | | Java | Cava | ConsenSys | [https://www.github.com/ConsenSys/cava/tree/master/ssz](https://www.github.com/ConsenSys/cava/tree/master/ssz) | | Go | Prysm | Prysmatic Labs | [https://github.com/prysmaticlabs/go-ssz](https://github.com/prysmaticlabs/go-ssz) | | Swift | Yeeth | Dean Eigenmann | [https://github.com/yeeth/SimpleSerialize.swift](https://github.com/yeeth/SimpleSerialize.swift) | From 7e04c70ca14dbbb023e2c4bde033a07a09e8eca6 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 24 Jun 2020 16:53:53 -0700 Subject: [PATCH 176/236] Update beacon-chain.md --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 5a5200c9e8..e103d51f4e 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -569,7 +569,7 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque seed=seed, index=0, count=get_active_shard_count(beacon_state), - )[:TARGET_COMMITTEE_SIZE] + )[:LIGHT_CLIENT_COMMITTEE_SIZE] ``` #### `get_shard_proposer_index` From 79b6bc616d1ac6c6e2aa6864b2354190fc47534a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 25 Jun 2020 10:58:04 +0800 Subject: [PATCH 177/236] PR feedback from danny --- specs/phase1/shard-transition.md | 2 +- .../pyspec/eth2spec/test/phase1/sanity/test_blocks.py | 8 ++++---- .../eth2spec/test/phase1/sanity/test_shard_blocks.py | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 1ca5a9ab8a..7deaf9f095 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -66,7 +66,7 @@ def verify_shard_block_signature(beacon_parent_state: BeaconState, ## Shard state transition function -The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_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. State transitions that cause a `uint64` overflow or underflow are also considered invalid. +The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_state_transition(shard_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. State transitions that cause a `uint64` overflow or underflow are also considered invalid. ```python def shard_state_transition(shard_state: ShardState, diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 3cb2fe6c01..2c391a6604 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -48,10 +48,10 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm yield 'block', beacon_block yield 'post', None return - else: - state_transition_and_sign_block(spec, state, beacon_block) - yield 'block', beacon_block - yield 'post', None + + state_transition_and_sign_block(spec, state, beacon_block) + yield 'block', beacon_block + yield 'post', state for shard in range(spec.get_active_shard_count(state)): post_shard_state = state.shard_states[shard] diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index 4d07f340a6..e8c5f91da1 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -17,14 +17,12 @@ def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, validate=True, valid=True): pre_shard_state = shard_state.copy() + yield 'pre', pre_shard_state yield 'signed_shard_block', signed_shard_block yield 'validate', validate yield 'beacon_parent_state', beacon_parent_state - if validate is False: - beacon_parent_state = None - if not valid: expect_assertion_error(lambda: spec.shard_state_transition( shard_state, signed_shard_block, validate=validate, beacon_parent_state=beacon_parent_state) From 2a7b5f7e68569416b55a4d6faa2dba85b0925e00 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 25 Jun 2020 11:14:25 +0800 Subject: [PATCH 178/236] PR feedback from Danny: nuke optional beacon_parent_state --- specs/phase1/shard-fork-choice.md | 4 +--- specs/phase1/shard-transition.md | 14 +++++++------- specs/phase1/validator.md | 2 +- .../test/phase1/sanity/test_shard_blocks.py | 12 ++++-------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 8d64eb842e..ba5bc26172 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -170,9 +170,7 @@ def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: Si # Check the block is valid and compute the post-state shard_state = shard_parent_state.copy() - shard_state_transition( - shard_state, signed_shard_block, - validate=True, beacon_parent_state=beacon_parent_state) + shard_state_transition(shard_state, signed_shard_block, beacon_parent_state, validate_result=True) # Add new block to the store shard_store.blocks[hash_tree_root(shard_block)] = shard_block diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 7deaf9f095..e273f2ddc2 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -66,16 +66,16 @@ def verify_shard_block_signature(beacon_parent_state: BeaconState, ## Shard state transition function -The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_state_transition(shard_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. State transitions that cause a `uint64` overflow or underflow are also considered invalid. +The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_state_transition(shard_state, signed_block, beacon_parent_state)`, where `beacon_parent_state` is the parent beacon state of the `signed_block`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. ```python def shard_state_transition(shard_state: ShardState, signed_block: SignedShardBlock, - validate: bool = True, - beacon_parent_state: Optional[BeaconState] = None) -> ShardState: - if validate: - assert beacon_parent_state is not None - assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message) + beacon_parent_state: BeaconState, + validate_result: bool = True) -> ShardState: + assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message) + + if validate_result: assert verify_shard_block_signature(beacon_parent_state, signed_block) process_shard_block(shard_state, signed_block.message) @@ -133,7 +133,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - shard_state_transition(shard_state, block, validate=False) + process_shard_block(shard_state, block.message) if shard_state != transition.shard_states[offset_index]: return True diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 8b7ade60b9..1307652683 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -299,7 +299,7 @@ def get_shard_transition_fields( shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) shard_state = shard_state.copy() - shard_state_transition(shard_state, shard_block, validate=False) + process_shard_block(shard_state, shard_block.message) shard_states.append(shard_state) shard_block_lengths.append(len(shard_block.message.body)) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index e8c5f91da1..99696dd456 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -13,25 +13,21 @@ from eth2spec.test.helpers.state import transition_to_valid_shard_slot -def run_shard_blocks(spec, shard_state, signed_shard_block, - beacon_parent_state, - validate=True, valid=True): +def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, valid=True): pre_shard_state = shard_state.copy() yield 'pre', pre_shard_state yield 'signed_shard_block', signed_shard_block - yield 'validate', validate yield 'beacon_parent_state', beacon_parent_state if not valid: - expect_assertion_error(lambda: spec.shard_state_transition( - shard_state, signed_shard_block, validate=validate, beacon_parent_state=beacon_parent_state) + expect_assertion_error( + lambda: spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state) ) yield 'post', None return - spec.shard_state_transition(shard_state, signed_shard_block, - validate=validate, beacon_parent_state=beacon_parent_state) + spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state) yield 'post', shard_state # Verify `process_shard_block` From cc130990b6f3c4d6f789c70def2567b861c04eb4 Mon Sep 17 00:00:00 2001 From: Joanne Fuller <43776922+booleanfunction@users.noreply.github.com> Date: Thu, 25 Jun 2020 13:20:57 +1000 Subject: [PATCH 179/236] Clarify wording of pack, pack_bits and merkleize --- ssz/simple-serialize.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index a9d1da56e3..a58a4c1885 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -213,13 +213,17 @@ We first define helper functions: * `List[B, N]` and `Vector[B, N]`, where `B` is a basic type: `(N * size_of(B) + 31) // 32` (dividing by chunk size, rounding up) * `List[C, N]` and `Vector[C, N]`, where `C` is a composite type: `N` * containers: `len(fields)` -* `pack(value)`: given ordered objects of the same basic type, serialize them, pack them into `BYTES_PER_CHUNK`-byte chunks, right-pad the last chunk with zero bytes, and return the chunks. -* `pack_bits(bits)`: Given the `bits` of bitlist or bitvector, get `bitfield_bytes` by packing them in bytes and aligning to the start. The length-delimiting bit for bitlists is excluded. And then pack `bitfield_bytes` into `BYTES_PER_CHUNK`-byte chunks, right-pad the last chunk with zero bytes, and return the chunks. +* `pack(values)`: Given ordered objects of the same basic type: + 1. Serialize the values into bytes. + 1. If not aligned to a multiple of `BYTES_PER_CHUNK` bytes, right-pad with zeroes to the next multiple. + 1. Partition the bytes into `BYTES_PER_CHUNK`-byte chunks. + 1. Return the chunks. +* `pack_bits(bits)`: Given the bits of bitlist or bitvector, get `bitfield_bytes` by packing them in bytes and aligning to the start. The length-delimiting bit for bitlists is excluded. Then return `pack(bitfield_bytes)`. * `next_pow_of_two(i)`: get the next power of 2 of `i`, if not already a power of 2, with 0 mapping to 1. Examples: `0->1, 1->1, 2->2, 3->4, 4->4, 6->8, 9->16` * `merkleize(chunks, limit=None)`: Given ordered `BYTES_PER_CHUNK`-byte chunks, merkleize the chunks, and return the root: - * The merkleization depends on the effective input, which can be padded/limited: + * The merkleization depends on the effective input, which must be padded/limited: - if no limit: pad the `chunks` with zeroed chunks to `next_pow_of_two(len(chunks))` (virtually for memory efficiency). - - if `limit > len(chunks)`, pad the `chunks` with zeroed chunks to `next_pow_of_two(limit)` (virtually for memory efficiency). + - if `limit >= len(chunks)`, pad the `chunks` with zeroed chunks to `next_pow_of_two(limit)` (virtually for memory efficiency). - if `limit < len(chunks)`: do not merkleize, input exceeds limit. Raise an error instead. * Then, merkleize the chunks (empty input is padded to 1 zero chunk): - If `1` chunk: the root is the chunk itself. From a061758a664f74f992d5b7ec32913e8ef8bb768e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 25 Jun 2020 17:52:05 +0800 Subject: [PATCH 180/236] Use `encode_bytes` to implement `int_to_bytes` Rename `bytes_to_int` to `bytes_to_uint64` Use `encode_bytes` to implement `int_to_bytes` Rename `int_to_bytes` to `uint_to_bytes` and move it to `ssz_impl.py` --- setup.py | 8 ++--- specs/phase0/beacon-chain.md | 36 +++++++++---------- specs/phase0/validator.md | 2 +- specs/phase1/beacon-chain.md | 4 +-- specs/phase1/validator.md | 2 +- .../pyspec/eth2spec/utils/ssz/ssz_impl.py | 5 +++ 6 files changed, 30 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index cc3b00da93..86985b96db 100644 --- a/setup.py +++ b/setup.py @@ -94,9 +94,9 @@ def get_spec(file_name: str) -> SpecObject: from lru import LRU -from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, uint_to_bytes from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint64, + View, boolean, Container, List, Vector, uint8, uint32, uint64, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils import bls @@ -118,9 +118,9 @@ def get_spec(file_name: str) -> SpecObject: from lru import LRU -from eth2spec.utils.ssz.ssz_impl import hash_tree_root +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, uint_to_bytes from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint64, uint8, bit, + View, boolean, Container, List, Vector, uint8, uint32, uint64, bit, ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils import bls diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index cd52762d56..df36618102 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -55,8 +55,8 @@ - [Math](#math) - [`integer_squareroot`](#integer_squareroot) - [`xor`](#xor) - - [`int_to_bytes`](#int_to_bytes) - - [`bytes_to_int`](#bytes_to_int) + - [`uint_to_bytes`](#uint_to_bytes) + - [`bytes_to_uint64`](#bytes_to_uint64) - [Crypto](#crypto) - [`hash`](#hash) - [`hash_tree_root`](#hash_tree_root) @@ -576,20 +576,14 @@ def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2)) ``` -#### `int_to_bytes` +#### `uint_to_bytes` -```python -def int_to_bytes(n: uint64, length: uint64) -> bytes: - """ - Return the ``length``-byte serialization of ``n`` in ``ENDIANNESS``-endian. - """ - return n.to_bytes(length, ENDIANNESS) -``` +`def uint_to_bytes(n: uint8) -> bytes` is a function for serializing the `uint` type object to bytes in ``ENDIANNESS``-endian. The expected length of the output is the byte-length of the `uint` type. -#### `bytes_to_int` +#### `bytes_to_uint64` ```python -def bytes_to_int(data: bytes) -> uint64: +def bytes_to_uint64(data: bytes) -> uint64: """ Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian. """ @@ -732,11 +726,15 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) # See the 'generalized domain' algorithm on page 3 - for current_round in range(SHUFFLE_ROUND_COUNT): - pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=1))[0:8]) % index_count - flip = (pivot + index_count - index) % index_count + for current_round in map(uint8, range(SHUFFLE_ROUND_COUNT)): + pivot = bytes_to_uint64(hash(seed + uint_to_bytes(current_round))[0:8]) % index_count + flip = uint64((pivot + index_count - index) % index_count) position = max(index, flip) - source = hash(seed + int_to_bytes(current_round, length=1) + int_to_bytes(position // 256, length=4)) + source = hash( + seed + + uint_to_bytes(current_round) + + uint_to_bytes(uint32(position // 256)) + ) byte = source[(position % 256) // 8] bit = (byte >> (position % 8)) % 2 index = flip if bit else index @@ -756,7 +754,7 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] i = 0 while True: candidate_index = indices[compute_shuffled_index(i % len(indices), len(indices), seed)] - random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32] + random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] effective_balance = state.validators[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index @@ -945,7 +943,7 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes Return the seed at ``epoch``. """ mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow - return hash(domain_type + int_to_bytes(epoch, length=8) + mix) + return hash(domain_type + uint_to_bytes(epoch) + mix) ``` #### `get_committee_count_per_slot` @@ -986,7 +984,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: Return the beacon proposer index at the current slot. """ epoch = get_current_epoch(state) - seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + int_to_bytes(state.slot, length=8)) + seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot)) indices = get_active_validator_indices(state, epoch) return compute_proposer_index(state, indices, seed) ``` diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index e325f75f61..6a895e1fad 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -465,7 +465,7 @@ def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSigna def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: committee = get_beacon_committee(state, slot, index) modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) - return bytes_to_int(hash(slot_signature)[0:8]) % modulo == 0 + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 ``` #### Construct aggregate diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index e103d51f4e..43cdcff78e 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -581,8 +581,8 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard """ epoch = compute_epoch_at_slot(slot) committee = get_shard_committee(beacon_state, epoch, shard) - seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE) + int_to_bytes(slot, length=8)) - r = bytes_to_int(seed[:8]) + seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE) + uint_to_bytes(slot)) + r = bytes_to_uint64(seed[:8]) return committee[r % len(committee)] ``` diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 8b7ade60b9..26e769dff2 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -463,7 +463,7 @@ def get_light_client_slot_signature(state: BeaconState, slot: Slot, privkey: int def is_light_client_aggregator(state: BeaconState, slot: Slot, slot_signature: BLSSignature) -> bool: committee = get_light_client_committee(state, compute_epoch_at_slot(slot)) modulo = max(1, len(committee) // TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT) - return bytes_to_int(hash(slot_signature)[0:8]) % modulo == 0 + return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 ``` #### Construct aggregate diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py index 34e6c4ee8c..a47c77c33d 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py @@ -1,3 +1,4 @@ +from remerkleable.basic import uint from remerkleable.core import View from remerkleable.byte_arrays import Bytes32 @@ -8,3 +9,7 @@ def serialize(obj: View) -> bytes: def hash_tree_root(obj: View) -> Bytes32: return Bytes32(obj.get_backing().merkle_root()) + + +def uint_to_bytes(n: uint) -> bytes: + return serialize(n) From dc02bbc14223e225726ef77ac7fbde6192f08c70 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 26 Jun 2020 00:42:29 +0800 Subject: [PATCH 181/236] Add more shard block tests --- .../test/phase1/sanity/test_shard_blocks.py | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index 99696dd456..21f5cb18ad 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -10,7 +10,7 @@ sign_shard_block, ) from eth2spec.test.helpers.shard_transitions import is_full_crosslink -from eth2spec.test.helpers.state import transition_to_valid_shard_slot +from eth2spec.test.helpers.state import next_slot, transition_to_valid_shard_slot, transition_to def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, valid=True): @@ -138,6 +138,66 @@ def test_invalid_proposer_index(spec, state): yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_out_of_bound_offset(spec, state): + # TODO: Handle this edge case properly + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + slot = ( + beacon_state.shard_states[shard].slot + + spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1] + + 1 # out-of-bound + ) + transition_to(spec, beacon_state, slot) + + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_invalid_offset(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + # 4 is not in `SHARD_BLOCK_OFFSETS` + shard = 0 + slot = beacon_state.shard_states[shard].slot + 4 + transition_to(spec, beacon_state, slot) + + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_empty_block_body(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, body=b'', signed=True) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + # # verify_shard_block_signature # @@ -157,3 +217,54 @@ def test_invalid_signature(spec, state): signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False) yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +# +# Other cases +# + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_max_offset(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + slot = beacon_state.shard_states[shard].slot + spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1] + transition_to(spec, beacon_state, slot) + + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_pending_shard_parent_block(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + # Block N + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block_1 = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + _, _, _, _ = run_shard_blocks(spec, shard_state, signed_shard_block_1, beacon_state) + + # Block N+1 + next_slot(spec, beacon_state) + signed_shard_block_2 = build_shard_block( + spec, beacon_state, shard, + slot=beacon_state.slot, shard_parent_state=shard_state, signed=True + ) + + assert signed_shard_block_2.message.shard_parent_root == shard_state.latest_block_root + assert signed_shard_block_2.message.slot == signed_shard_block_1.message.slot + 1 + yield from run_shard_blocks(spec, shard_state, signed_shard_block_2, beacon_state) From 94c231cf986a268320d658a8c6dd96c14a0b57e5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 26 Jun 2020 01:12:36 +0800 Subject: [PATCH 182/236] Fix `ShardTransition.shard_data_roots` and add test 1. Fix `ShardTransition.shard_data_roots`: use `get_block_data_merkle_root` helper to calculate it. 2. Rework `get_valid_custody_chunk_response` testing helper: accept `block_length_or_custody_data` 3. Add `test_with_custody_challenge_and_response` test --- setup.py | 7 ++ specs/phase1/validator.md | 4 +- .../pyspec/eth2spec/test/helpers/custody.py | 21 +++--- .../test_process_chunk_challenge.py | 9 ++- .../test_process_custody_final_updates.py | 3 +- .../test/phase1/sanity/test_blocks.py | 66 +++++++++++++++++++ 6 files changed, 94 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index cc3b00da93..8661f5e52c 100644 --- a/setup.py +++ b/setup.py @@ -221,6 +221,13 @@ def wrapper(*args, **kw): # type: ignore PHASE1_SUNDRY_FUNCTIONS = ''' + +def get_block_data_merkle_root(data: ByteList) -> Root: + # To get the Merkle root of the block data, we need the Merkle root without the length Mixing + # The below implements this in the Remerkleable framework + return data.get_backing().get_left().merkle_root() + + _get_start_shard = get_start_shard get_start_shard = cache_this( lambda state, slot: (state.validators.hash_tree_root(), slot), diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 1307652683..aa5e07f1a8 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -275,6 +275,8 @@ Set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`. Set `shard_transition` to the value returned by `get_shard_transition(head_state, shard, shard_blocks)`. +`get_block_data_merkle_root(data: ByteList) -> Root` is the function that returns the Merkle root of the block data without the length mixing. + ```python def get_shard_transition_fields( beacon_state: BeaconState, @@ -294,7 +296,7 @@ def get_shard_transition_fields( for slot in offset_slots: if slot in shard_block_slots: shard_block = shard_blocks[shard_block_slots.index(slot)] - shard_data_roots.append(hash_tree_root(shard_block.message.body)) + shard_data_roots.append(get_block_data_merkle_root(shard_block.message.body)) else: shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard)) shard_data_roots.append(Root()) diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index d25b91a410..d535a23851 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -136,17 +136,16 @@ def build_proof(anchor, leaf_index): return list(reversed(proof)) -def get_block_data_merkle_root(data_as_bytelist): - # To get the Merkle root of the block data, we need the Merkle root without the length Mixing - # The below implements this in the Remerkleable framework - return data_as_bytelist.get_backing().get_left().merkle_root() - - -def get_valid_custody_chunk_response(spec, state, chunk_challenge, block_length, challenge_index, +def get_valid_custody_chunk_response(spec, state, chunk_challenge, challenge_index, + block_length_or_custody_data, invalid_chunk_data=False): - custody_data = get_custody_test_vector(block_length) + if isinstance(block_length_or_custody_data, int): + custody_data = get_custody_test_vector(block_length_or_custody_data) + else: + custody_data = block_length_or_custody_data + custody_data_block = ByteList[spec.MAX_SHARD_BLOCK_SIZE](custody_data) - chunks = custody_chunkify(spec, custody_data) + chunks = custody_chunkify(spec, custody_data_block) chunk_index = chunk_challenge.chunk_index @@ -166,7 +165,7 @@ def get_custody_test_vector(bytelength, offset=0): def get_sample_shard_transition(spec, start_slot, block_lengths): - b = [get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) + b = [spec.get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) for x in block_lengths] shard_transition = spec.ShardTransition( start_slot=start_slot, @@ -201,5 +200,5 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, block_lengths[0], slashable=slashable) block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) - shard_transition.shard_data_roots[0] = get_block_data_merkle_root(block_data) + shard_transition.shard_data_roots[0] = spec.get_block_data_merkle_root(block_data) return shard_transition, slashable_test_vector diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py index c9adabb62b..4ef1d667a2 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py @@ -242,7 +242,8 @@ def test_custody_response(spec, state): chunk_challenge_index = state.custody_chunk_challenge_index - 1 - custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + custody_response = get_valid_custody_chunk_response( + spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) yield from run_custody_chunk_response_processing(spec, state, custody_response) @@ -270,7 +271,8 @@ def test_custody_response_multiple_epochs(spec, state): chunk_challenge_index = state.custody_chunk_challenge_index - 1 - custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + custody_response = get_valid_custody_chunk_response( + spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) yield from run_custody_chunk_response_processing(spec, state, custody_response) @@ -298,6 +300,7 @@ def test_custody_response_many_epochs(spec, state): chunk_challenge_index = state.custody_chunk_challenge_index - 1 - custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + custody_response = get_valid_custody_chunk_response( + spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) yield from run_custody_chunk_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py index 630ddc3a7e..93fea19cf2 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py @@ -159,7 +159,8 @@ def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state) assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH chunk_challenge_index = state.custody_chunk_challenge_index - 1 - custody_response = get_valid_custody_chunk_response(spec, state, challenge, 2**15 // 3, chunk_challenge_index) + custody_response = get_valid_custody_chunk_response( + spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) _, _, _ = run_custody_chunk_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 2c391a6604..2f4c0b67b6 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -7,8 +7,13 @@ ) from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import build_empty_block +from eth2spec.test.helpers.custody import ( + get_valid_chunk_challenge, + get_valid_custody_chunk_response, +) from eth2spec.test.helpers.shard_block import ( build_shard_block, + get_committee_index_of_shard, get_sample_shard_block_body, get_shard_transitions, ) @@ -16,6 +21,25 @@ from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to +def run_beacon_block(spec, state, block, valid=True): + yield 'pre', state.copy() + + if not valid: + state_transition_and_sign_block(spec, state, block, expect_fail=True) + yield 'block', block + yield 'post', None + return + + state_transition_and_sign_block(spec, state, block) + yield 'block', block + yield 'post', state + + +# +# Beacon block with non-empty shard transitions +# + + def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True): transition_to(spec, state, state.slot + target_len_offset_slot) @@ -102,3 +126,45 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): assert state.shard_states[shard].slot == state.slot - 1 yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard) + + +# +# Beacon block with custody operations +# + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_with_custody_challenge_and_response(spec, state): + # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # skip + return + + state = transition_to_valid_shard_slot(spec, state) + + # build shard block + shard = 0 + committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) + body = get_sample_shard_block_body(spec) + shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) + shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} + shard_transitions = get_shard_transitions(spec, state, shard_block_dict) + attestation = get_valid_on_time_attestation( + spec, state, index=committee_index, + shard_transition=shard_transitions[shard], signed=True, + ) + + block = build_empty_block(spec, state, slot=state.slot + 1) + block.body.attestations = [attestation] + block.body.shard_transitions = shard_transitions + + # Custody operations + challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transitions[shard]) + block.body.chunk_challenges = [challenge] + chunk_challenge_index = state.custody_chunk_challenge_index + custody_response = get_valid_custody_chunk_response( + spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=body) + block.body.chunk_challenge_responses = [custody_response] + + yield from run_beacon_block(spec, state, block) From 7f3e6564ebb277868b3f7598a56c4eedf180d029 Mon Sep 17 00:00:00 2001 From: Joanne Fuller <43776922+booleanfunction@users.noreply.github.com> Date: Fri, 26 Jun 2020 13:32:56 +1000 Subject: [PATCH 183/236] Update ssz/simple-serialize.md Co-authored-by: Hsiao-Wei Wang --- ssz/simple-serialize.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index a58a4c1885..688c6a210f 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -215,9 +215,9 @@ We first define helper functions: * containers: `len(fields)` * `pack(values)`: Given ordered objects of the same basic type: 1. Serialize the values into bytes. - 1. If not aligned to a multiple of `BYTES_PER_CHUNK` bytes, right-pad with zeroes to the next multiple. - 1. Partition the bytes into `BYTES_PER_CHUNK`-byte chunks. - 1. Return the chunks. + 2. If not aligned to a multiple of `BYTES_PER_CHUNK` bytes, right-pad with zeroes to the next multiple. + 3. Partition the bytes into `BYTES_PER_CHUNK`-byte chunks. + 4. Return the chunks. * `pack_bits(bits)`: Given the bits of bitlist or bitvector, get `bitfield_bytes` by packing them in bytes and aligning to the start. The length-delimiting bit for bitlists is excluded. Then return `pack(bitfield_bytes)`. * `next_pow_of_two(i)`: get the next power of 2 of `i`, if not already a power of 2, with 0 mapping to 1. Examples: `0->1, 1->1, 2->2, 3->4, 4->4, 6->8, 9->16` * `merkleize(chunks, limit=None)`: Given ordered `BYTES_PER_CHUNK`-byte chunks, merkleize the chunks, and return the root: From 588fcd0a6541261c611327fd8b019fa609e190b0 Mon Sep 17 00:00:00 2001 From: Joanne Fuller <43776922+booleanfunction@users.noreply.github.com> Date: Fri, 26 Jun 2020 13:33:23 +1000 Subject: [PATCH 184/236] Update ssz/simple-serialize.md Co-authored-by: Hsiao-Wei Wang --- ssz/simple-serialize.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 688c6a210f..4f0c5c9faf 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -214,7 +214,7 @@ We first define helper functions: * `List[C, N]` and `Vector[C, N]`, where `C` is a composite type: `N` * containers: `len(fields)` * `pack(values)`: Given ordered objects of the same basic type: - 1. Serialize the values into bytes. + 1. Serialize `values` into bytes. 2. If not aligned to a multiple of `BYTES_PER_CHUNK` bytes, right-pad with zeroes to the next multiple. 3. Partition the bytes into `BYTES_PER_CHUNK`-byte chunks. 4. Return the chunks. From 531184f42bbda5a5d2e8244b583014bbd2f2225b Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 26 Jun 2020 15:41:47 +0200 Subject: [PATCH 185/236] Infer types where possible, e.g. uint64+uint64=uint64 --- specs/phase0/beacon-chain.md | 37 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index ec4751fc38..df9390964c 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -561,9 +561,9 @@ def integer_squareroot(n: uint64) -> uint64: x = n y = (x + 1) // 2 while y < x: - x = uint64(y) + x = y y = (x + n // x) // 2 - return uint64(x) + return x ``` #### `xor` @@ -732,15 +732,11 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) # See the 'generalized domain' algorithm on page 3 - for current_round in map(uint64, range(SHUFFLE_ROUND_COUNT)): + for current_round in range(SHUFFLE_ROUND_COUNT): pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=1))[0:8]) % index_count - flip = uint64((pivot + index_count - index) % index_count) + flip = (pivot + index_count - index) % index_count position = max(index, flip) - source = hash( - seed - + int_to_bytes(current_round, length=1) - + int_to_bytes(uint64(position // 256), length=4) - ) + source = hash(seed + int_to_bytes(current_round, length=1) + int_to_bytes(position // 256, length=4)) byte = source[(position % 256) // 8] bit = (byte >> (position % 8)) % 2 index = flip if bit else index @@ -757,10 +753,11 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] """ assert len(indices) > 0 MAX_RANDOM_BYTE = 2**8 - 1 - i = 0 + i = uint64(0) + total = uint64(len(indices)) while True: - candidate_index = indices[compute_shuffled_index(uint64(i % len(indices)), uint64(len(indices)), seed)] - random_byte = hash(seed + int_to_bytes(uint64(i // 32), length=8))[i % 32] + candidate_index = indices[compute_shuffled_index(i % total, total, seed)] + random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32] effective_balance = state.validators[candidate_index].effective_balance if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: return candidate_index @@ -938,7 +935,7 @@ def get_validator_churn_limit(state: BeaconState) -> uint64: Return the validator churn limit for the current epoch. """ active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices) // CHURN_LIMIT_QUOTIENT)) + return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT) ``` #### `get_seed` @@ -961,7 +958,7 @@ def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: """ return max(uint64(1), min( MAX_COMMITTEES_PER_SLOT, - uint64(len(get_active_validator_indices(state, epoch)) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE), + uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, )) ``` @@ -977,8 +974,8 @@ def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) return compute_committee( indices=get_active_validator_indices(state, epoch), seed=get_seed(state, epoch, DOMAIN_BEACON_ATTESTER), - index=uint64((slot % SLOTS_PER_EPOCH) * committees_per_slot + index), - count=uint64(committees_per_slot * SLOTS_PER_EPOCH), + index=(slot % SLOTS_PER_EPOCH) * committees_per_slot + index, + count=committees_per_slot * SLOTS_PER_EPOCH, ) ``` @@ -1501,12 +1498,12 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ - Gwei(source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i]) + source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] for i in range(len(state.validators)) ] penalties = [ - Gwei(source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i]) + source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] for i in range(len(state.validators)) ] @@ -1773,13 +1770,13 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: assert is_valid_merkle_branch( leaf=hash_tree_root(deposit.data), branch=deposit.proof, - depth=uint64(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, ) # Deposits must be processed in order - state.eth1_deposit_index = uint64(state.eth1_deposit_index + 1) + state.eth1_deposit_index += 1 pubkey = deposit.data.pubkey amount = deposit.data.amount From 3fb0257cbb8cd95336d4b57fb42fa5e02a74baf4 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 26 Jun 2020 15:55:07 +0200 Subject: [PATCH 186/236] update remerkleable for uint and mypy improvements --- setup.py | 2 +- tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 85d1a95eab..801f215d08 100644 --- a/setup.py +++ b/setup.py @@ -519,7 +519,7 @@ def run(self): "py_ecc==4.0.0", "milagro_bls_binding==1.3.0", "dataclasses==0.6", - "remerkleable==0.1.16", + "remerkleable==0.1.17", "ruamel.yaml==0.16.5", "lru-dict==1.1.6" ] diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py index e6536c7481..b3a0b9962e 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -5,4 +5,4 @@ from remerkleable.basic import boolean, bit, uint, byte, uint8, uint16, uint32, uint64, uint128, uint256 from remerkleable.bitfields import Bitvector, Bitlist from remerkleable.byte_arrays import ByteVector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, ByteList -from remerkleable.core import BasicView, View, TypeDef +from remerkleable.core import BasicView, View From 3b7617f51ace8161b5fc89ea1eaf2ee102f200c5 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 26 Jun 2020 16:13:38 +0200 Subject: [PATCH 187/236] make extracted byte uint8 for bitshift, do not use negative slice indexing, avoid negative comparison in test --- setup.py | 2 +- specs/phase0/beacon-chain.md | 4 ++-- .../epoch_processing/test_process_rewards_and_penalties.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 801f215d08..3a4fd1e187 100644 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def get_spec(file_name: str) -> SpecObject: from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint64, + View, boolean, Container, List, Vector, uint64, uint8, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils import bls diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index df9390964c..c7dd25d3ac 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -737,7 +737,7 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> flip = (pivot + index_count - index) % index_count position = max(index, flip) source = hash(seed + int_to_bytes(current_round, length=1) + int_to_bytes(position // 256, length=4)) - byte = source[(position % 256) // 8] + byte = uint8(source[(position % 256) // 8]) bit = (byte >> (position % 8)) % 2 index = flip if bit else index @@ -1317,7 +1317,7 @@ def process_justification_and_finalization(state: BeaconState) -> None: # Process justifications state.previous_justified_checkpoint = state.current_justified_checkpoint - state.justification_bits[1:] = state.justification_bits[:-1] + state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1] state.justification_bits[0] = 0b0 matching_target_attestations = get_matching_target_attestations(state, previous_epoch) # Previous epoch if get_attesting_balance(state, matching_target_attestations) * 3 >= get_total_active_balance(state) * 2: diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index d3744f897f..72bdd3e92a 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -48,7 +48,7 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state): attestation = get_valid_attestation(spec, state, signed=True) attestations.append(attestation) # fill each created slot in state after inclusion delay - if slot - spec.MIN_ATTESTATION_INCLUSION_DELAY >= 0: + if slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: include_att = attestations[slot - spec.MIN_ATTESTATION_INCLUSION_DELAY] add_attestations_to_state(spec, state, [include_att], state.slot) next_slot(spec, state) From 1d954ee9bd0c2a925b02666ec4dbde237e2857c7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 29 Jun 2020 12:33:52 +0800 Subject: [PATCH 188/236] PR feedback from @ericsson49 Co-authored-by: Alex Vlasov --- specs/phase0/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index df36618102..ba701c2b17 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -578,7 +578,7 @@ def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: #### `uint_to_bytes` -`def uint_to_bytes(n: uint8) -> bytes` is a function for serializing the `uint` type object to bytes in ``ENDIANNESS``-endian. The expected length of the output is the byte-length of the `uint` type. +`def uint_to_bytes(n: uint) -> bytes` is a function for serializing the `uint` type object to bytes in ``ENDIANNESS``-endian. The expected length of the output is the byte-length of the `uint` type. #### `bytes_to_uint64` From 4b239e94b742a89df26fff5a74feac8815f7463c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 29 Jun 2020 12:50:01 +0800 Subject: [PATCH 189/236] Mix PR feedback from Danny and Proto --- specs/phase0/beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index ba701c2b17..1994b86910 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -726,13 +726,13 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> # Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf) # See the 'generalized domain' algorithm on page 3 - for current_round in map(uint8, range(SHUFFLE_ROUND_COUNT)): - pivot = bytes_to_uint64(hash(seed + uint_to_bytes(current_round))[0:8]) % index_count - flip = uint64((pivot + index_count - index) % index_count) + for current_round in range(SHUFFLE_ROUND_COUNT): + pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count + flip = (pivot + index_count - index) % index_count position = max(index, flip) source = hash( seed - + uint_to_bytes(current_round) + + uint_to_bytes(uint8(current_round)) + uint_to_bytes(uint32(position // 256)) ) byte = source[(position % 256) // 8] From c871733bf35bc6644dbe73d389e6640ad9959a78 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 29 Jun 2020 19:48:07 +0800 Subject: [PATCH 190/236] Fix yield block --- .../eth2spec/test/phase1/sanity/test_blocks.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 2f4c0b67b6..508feb65d4 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -25,13 +25,13 @@ def run_beacon_block(spec, state, block, valid=True): yield 'pre', state.copy() if not valid: - state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'block', block + signed_beacon_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + yield 'block', signed_beacon_block yield 'post', None return - state_transition_and_sign_block(spec, state, block) - yield 'block', block + signed_beacon_block = state_transition_and_sign_block(spec, state, block) + yield 'block', signed_beacon_block yield 'post', state @@ -73,8 +73,8 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm yield 'post', None return - state_transition_and_sign_block(spec, state, beacon_block) - yield 'block', beacon_block + signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) + yield 'block', signed_beacon_block yield 'post', state for shard in range(spec.get_active_shard_count(state)): @@ -159,7 +159,7 @@ def test_with_custody_challenge_and_response(spec, state): block.body.attestations = [attestation] block.body.shard_transitions = shard_transitions - # Custody operations + # CustodyChunkChallenge and CustodyChunkResponse operations challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transitions[shard]) block.body.chunk_challenges = [challenge] chunk_challenge_index = state.custody_chunk_challenge_index From 235175d937fee37b3ba6f4a469a111423d348c6c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 29 Jun 2020 19:54:24 +0800 Subject: [PATCH 191/236] Add `CustodyKeyReveal` and `EarlyDerivedSecretReveal` tests --- .../test/phase1/sanity/test_blocks.py | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 508feb65d4..621fbb55de 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -10,6 +10,8 @@ from eth2spec.test.helpers.custody import ( get_valid_chunk_challenge, get_valid_custody_chunk_response, + get_valid_custody_key_reveal, + get_valid_early_derived_secret_reveal, ) from eth2spec.test.helpers.shard_block import ( build_shard_block, @@ -135,7 +137,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): @with_all_phases_except([PHASE0]) @spec_state_test -def test_with_custody_challenge_and_response(spec, state): +def test_with_shard_transition_with_custody_challenge_and_response(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet if not is_full_crosslink(spec, state): # skip @@ -159,12 +161,37 @@ def test_with_custody_challenge_and_response(spec, state): block.body.attestations = [attestation] block.body.shard_transitions = shard_transitions - # CustodyChunkChallenge and CustodyChunkResponse operations + # CustodyChunkChallenge operation challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transitions[shard]) block.body.chunk_challenges = [challenge] + # CustodyChunkResponse operation chunk_challenge_index = state.custody_chunk_challenge_index custody_response = get_valid_custody_chunk_response( spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=body) block.body.chunk_challenge_responses = [custody_response] yield from run_beacon_block(spec, state, block) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_with_custody_key_reveal(spec, state): + state = transition_to_valid_shard_slot(spec, state) + transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) + + block = build_empty_block(spec, state, slot=state.slot + 1) + custody_key_reveal = get_valid_custody_key_reveal(spec, state) + block.body.custody_key_reveals = [custody_key_reveal] + + yield from run_beacon_block(spec, state, block) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_with_early_derived_secret_reveal(spec, state): + state = transition_to_valid_shard_slot(spec, state) + block = build_empty_block(spec, state, slot=state.slot + 1) + early_derived_secret_reveal = get_valid_early_derived_secret_reveal(spec, state) + block.body.early_derived_secret_reveals = [early_derived_secret_reveal] + + yield from run_beacon_block(spec, state, block) From d3f2344e45b9a8425514bca2e5a80dac77a7c664 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 29 Jun 2020 20:56:36 +0800 Subject: [PATCH 192/236] Add CustodySlashing block test and fix `get_attesting_indices` cache --- setup.py | 5 +- .../pyspec/eth2spec/test/helpers/custody.py | 2 +- .../test/phase1/sanity/test_blocks.py | 49 ++++++++++++++++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 8661f5e52c..948844fff3 100644 --- a/setup.py +++ b/setup.py @@ -216,7 +216,10 @@ def wrapper(*args, **kw): # type: ignore _get_attesting_indices = get_attesting_indices get_attesting_indices = cache_this( - lambda state, data, bits: (state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root()), + lambda state, data, bits: ( + state.randao_mixes.hash_tree_root(), + state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() + ), _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)''' diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index d535a23851..4cb9947fa7 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -191,7 +191,7 @@ def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=Tr offset = 0 while spec.compute_custody_bit(custody_secret, test_vector) != slashable: offset += 1 - test_vector = test_vector = get_custody_test_vector(length, offset) + test_vector = get_custody_test_vector(length, offset) return test_vector diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index 621fbb55de..f69cd47935 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -8,9 +8,12 @@ from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.custody import ( + get_custody_secret, + get_custody_slashable_test_vector, get_valid_chunk_challenge, get_valid_custody_chunk_response, get_valid_custody_key_reveal, + get_valid_custody_slashing, get_valid_early_derived_secret_reveal, ) from eth2spec.test.helpers.shard_block import ( @@ -175,7 +178,7 @@ def test_with_shard_transition_with_custody_challenge_and_response(spec, state): @with_all_phases_except([PHASE0]) @spec_state_test -def test_with_custody_key_reveal(spec, state): +def test_custody_key_reveal(spec, state): state = transition_to_valid_shard_slot(spec, state) transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) @@ -188,10 +191,52 @@ def test_with_custody_key_reveal(spec, state): @with_all_phases_except([PHASE0]) @spec_state_test -def test_with_early_derived_secret_reveal(spec, state): +def test_early_derived_secret_reveal(spec, state): state = transition_to_valid_shard_slot(spec, state) block = build_empty_block(spec, state, slot=state.slot + 1) early_derived_secret_reveal = get_valid_early_derived_secret_reveal(spec, state) block.body.early_derived_secret_reveals = [early_derived_secret_reveal] yield from run_beacon_block(spec, state, block) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_custody_slashing(spec, state): + # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # skip + return + + state = transition_to_valid_shard_slot(spec, state) + + # Build shard block + shard = 0 + committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) + # Create slashable shard block body + validator_index = spec.get_beacon_committee(state, state.slot, committee_index)[0] + custody_secret = get_custody_secret(spec, state, validator_index) + slashable_body = get_custody_slashable_test_vector(spec, custody_secret, length=100, slashable=True) + shard_block = build_shard_block(spec, state, shard, body=slashable_body, slot=state.slot, signed=True) + shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} + shard_transitions = get_shard_transitions(spec, state, shard_block_dict) + + attestation = get_valid_on_time_attestation( + spec, state, index=committee_index, + shard_transition=shard_transitions[shard], signed=True, + ) + block = build_empty_block(spec, state, slot=state.slot + 1) + block.body.attestations = [attestation] + block.body.shard_transitions = shard_transitions + + _, _, _ = run_beacon_block(spec, state, block) + + transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) + + block = build_empty_block(spec, state, slot=state.slot + 1) + custody_slashing = get_valid_custody_slashing( + spec, state, attestation, shard_transitions[shard], custody_secret, slashable_body + ) + block.body.custody_slashings = [custody_slashing] + + yield from run_beacon_block(spec, state, block) From 2c0595da010edee5d9dae8ca170fa6817f524554 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Jun 2020 00:11:43 +0800 Subject: [PATCH 193/236] PR feedback from Danny --- setup.py | 2 +- specs/phase1/custody-game.md | 7 ++++++- specs/phase1/validator.md | 2 -- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 948844fff3..ddf1785947 100644 --- a/setup.py +++ b/setup.py @@ -226,7 +226,7 @@ def wrapper(*args, **kw): # type: ignore PHASE1_SUNDRY_FUNCTIONS = ''' def get_block_data_merkle_root(data: ByteList) -> Root: - # To get the Merkle root of the block data, we need the Merkle root without the length Mixing + # To get the Merkle root of the block data, we need the Merkle root without the length mix-in # The below implements this in the Remerkleable framework return data.get_backing().get_left().merkle_root() diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 6b767193ac..a6030008d0 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -25,6 +25,7 @@ - [`CustodyKeyReveal`](#custodykeyreveal) - [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal) - [Helpers](#helpers) + - [`get_block_data_merkle_root`](#get_block_data_merkle_root) - [`replace_empty_or_append`](#replace_empty_or_append) - [`legendre_bit`](#legendre_bit) - [`get_custody_atoms`](#get_custody_atoms) @@ -182,6 +183,10 @@ class EarlyDerivedSecretReveal(Container): ## Helpers +### `get_block_data_merkle_root` + +`get_block_data_merkle_root(data: ByteList) -> Root` is the function that returns the Merkle root of the block data without the length mix-in. + ### `replace_empty_or_append` ```python @@ -515,7 +520,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition assert ( - custody_slashing.data.get_backing().get_left().merkle_root() + get_block_data_merkle_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index] ) assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index aa5e07f1a8..c91dcd09fd 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -275,8 +275,6 @@ Set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`. Set `shard_transition` to the value returned by `get_shard_transition(head_state, shard, shard_blocks)`. -`get_block_data_merkle_root(data: ByteList) -> Root` is the function that returns the Merkle root of the block data without the length mixing. - ```python def get_shard_transition_fields( beacon_state: BeaconState, From 98a9facab6806c890f902915e5919af82dc92828 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Jun 2020 00:16:00 +0800 Subject: [PATCH 194/236] PR feedback from Danny Co-authored-by: Danny Ryan --- .../core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index 21f5cb18ad..041b882abf 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -174,6 +174,7 @@ def test_invalid_offset(spec, state): # 4 is not in `SHARD_BLOCK_OFFSETS` shard = 0 slot = beacon_state.shard_states[shard].slot + 4 + assert slot not in spec.SHARD_BLOCK_OFFSETS transition_to(spec, beacon_state, slot) shard_state = beacon_state.shard_states[shard] From 96b71a19de3c67091b241cdd077e5e50270e6d43 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Jun 2020 16:58:56 +0800 Subject: [PATCH 195/236] Avoid Python-specific negative operation --- specs/phase0/validator.md | 2 +- specs/phase1/custody-game.md | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index e325f75f61..a3bbcac32a 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -307,7 +307,7 @@ def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Da valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] # Default vote on latest eth1 block data in the period range unless eth1 chain is not live - default_vote = votes_to_consider[-1] if any(votes_to_consider) else state.eth1_data + default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state.eth1_data return max( valid_votes, diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 6b767193ac..e454e28c0e 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -229,9 +229,11 @@ Given one set of data, return the custody atoms: each atom will be combined with ```python def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: - bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_ATOM) # right-padding - return [bytez[i:i + BYTES_PER_CUSTODY_ATOM] - for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM)] + bytez += b'\x00' * ((BYTES_PER_CUSTODY_ATOM - len(bytez)) % BYTES_PER_CUSTODY_ATOM) # right-padding + return [ + bytez[i:i + BYTES_PER_CUSTODY_ATOM] + for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM) + ] ``` ### `get_custody_secrets` From eaae70b3b376a9ebf5949156365503a77d050241 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Jun 2020 17:16:48 +0800 Subject: [PATCH 196/236] Minor refactoring --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/custody-game.md | 3 +-- specs/phase1/validator.md | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index c3ae4102ec..6e74337585 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -532,7 +532,7 @@ def get_active_shard_count(state: BeaconState) -> uint64: ```python def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: active_validators = get_active_validator_indices(state, get_current_epoch(state)) - return set([i for i in active_validators if state.online_countdown[i] != 0]) + return set(i for i in active_validators if state.online_countdown[i] != 0) # non-duplicate ``` #### `get_shard_committee` diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index e454e28c0e..8bcfc04335 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -554,7 +554,6 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed slash_validator(state, custody_slashing.whistleblower_index) ``` - ## Per-epoch processing ### Handling of reveal deadlines @@ -586,7 +585,7 @@ def process_custody_final_updates(state: BeaconState) -> None: # Reset withdrawable epochs if challenge records are empty records = state.custody_chunk_challenge_records - validator_indices_in_records = set([record.responder_index for record in records]) + validator_indices_in_records = set(record.responder_index for record in records) # non-duplicate for index, validator in enumerate(state.validators): if validator.exit_epoch != FAR_FUTURE_EPOCH: not_all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index 1307652683..954193d25e 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -162,7 +162,7 @@ def get_shard_winning_roots(state: BeaconState, committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) # Loop over all shard transition roots, looking for a winning root - shard_transition_roots = set([a.data.shard_transition_root for a in shard_attestations]) + shard_transition_roots = set(a.data.shard_transition_root for a in shard_attestations) # non-duplicate for shard_transition_root in sorted(shard_transition_roots): transition_attestations = [ a for a in shard_attestations From 966363890be5c9c788616615879b35b31b206439 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Jun 2020 19:16:18 +0800 Subject: [PATCH 197/236] PR feedback from @ericsson49: fix when `len(bytez) > BYTES_PER_CUSTODY_ATOM` --- specs/phase1/custody-game.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 8bcfc04335..6cc838935f 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -229,7 +229,8 @@ Given one set of data, return the custody atoms: each atom will be combined with ```python def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: - bytez += b'\x00' * ((BYTES_PER_CUSTODY_ATOM - len(bytez)) % BYTES_PER_CUSTODY_ATOM) # right-padding + length_remainder = len(bytez) % BYTES_PER_CUSTODY_ATOM + bytez += b'\x00' * ((BYTES_PER_CUSTODY_ATOM - length_remainder) % BYTES_PER_CUSTODY_ATOM) # right-padding return [ bytez[i:i + BYTES_PER_CUSTODY_ATOM] for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM) From 1876c9591b3f39524f4d4aaad1312196fa7cb0a3 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 30 Jun 2020 08:16:28 -0700 Subject: [PATCH 198/236] Update light committee for next slot --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index c3ae4102ec..ec3a5ce0fc 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1084,7 +1084,7 @@ def process_light_client_committee_updates(state: BeaconState) -> None: """ Update light client committees. """ - if get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: + if compute_epoch_at_slot(state.slot + 1) % 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) From c8752956ed79dbe84c1d8efe0675086f09210313 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 30 Jun 2020 08:44:09 -0700 Subject: [PATCH 199/236] use next epoch for new_committee? --- specs/phase1/beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index ec3a5ce0fc..e782573c35 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1084,8 +1084,9 @@ def process_light_client_committee_updates(state: BeaconState) -> None: """ Update light client committees. """ - if compute_epoch_at_slot(state.slot + 1) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: + next_epoch = compute_epoch_at_slot(state.slot + 1) + if next_epoch % 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) + new_committee = get_light_client_committee(state, next_epoch + LIGHT_CLIENT_COMMITTEE_PERIOD) state.next_light_committee = committee_to_compact_committee(state, new_committee) ``` From 36339de5110075d0ff4b392bab57927e9c9f3c9a Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 30 Jun 2020 09:11:13 -0700 Subject: [PATCH 200/236] Lint --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index e782573c35..e04c22f42d 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1084,7 +1084,7 @@ def process_light_client_committee_updates(state: BeaconState) -> None: """ Update light client committees. """ - next_epoch = compute_epoch_at_slot(state.slot + 1) + next_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) if next_epoch % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: state.current_light_committee = state.next_light_committee new_committee = get_light_client_committee(state, next_epoch + LIGHT_CLIENT_COMMITTEE_PERIOD) From 7674c76b611ef30e70e0007387e809a78d8bf7ea Mon Sep 17 00:00:00 2001 From: ericsson Date: Wed, 1 Jul 2020 17:23:05 +0300 Subject: [PATCH 201/236] get rid of `.message` as `block` is `ShardBlock` already --- specs/phase1/shard-transition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index e273f2ddc2..92e2c53336 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -133,7 +133,7 @@ def is_valid_fraud_proof(beacon_state: BeaconState, else: shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here. - process_shard_block(shard_state, block.message) + process_shard_block(shard_state, block) if shard_state != transition.shard_states[offset_index]: return True From ddca8c1a994bb7bcf48d66963d04e2bc848950e8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 2 Jul 2020 13:45:20 +0800 Subject: [PATCH 202/236] Kick cache --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3a67e55281..565b1c92f5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,39 +35,39 @@ commands: description: "Restore the cache with pyspec keys" steps: - restore_cached_venv: - venv_name: v19-pyspec + venv_name: v22-pyspec reqs_checksum: cache-{{ checksum "setup.py" }} save_pyspec_cached_venv: description: Save a venv into a cache with pyspec keys" steps: - save_cached_venv: - venv_name: v19-pyspec + venv_name: v22-pyspec reqs_checksum: cache-{{ checksum "setup.py" }} venv_path: ./venv restore_deposit_contract_compiler_cached_venv: description: "Restore the venv from cache for the deposit contract compiler" steps: - restore_cached_venv: - venv_name: v18-deposit-contract-compiler + venv_name: v23-deposit-contract-compiler reqs_checksum: cache-{{ checksum "deposit_contract/compiler/requirements.txt" }} save_deposit_contract_compiler_cached_venv: description: "Save the venv to cache for later use of the deposit contract compiler" steps: - save_cached_venv: - venv_name: v18-deposit-contract-compiler + venv_name: v23-deposit-contract-compiler reqs_checksum: cache-{{ checksum "deposit_contract/compiler/requirements.txt" }} venv_path: ./deposit_contract/compiler/venv restore_deposit_contract_tester_cached_venv: description: "Restore the venv from cache for the deposit contract tester" steps: - restore_cached_venv: - venv_name: v19-deposit-contract-tester + venv_name: v22-deposit-contract-tester reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "deposit_contract/tester/requirements.txt" }} save_deposit_contract_tester_cached_venv: description: "Save the venv to cache for later use of the deposit contract tester" steps: - save_cached_venv: - venv_name: v19-deposit-contract-tester + venv_name: v22-deposit-contract-tester reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "deposit_contract/tester/requirements.txt" }} venv_path: ./deposit_contract/tester/venv jobs: From 27fa15eb76e4a7c9f4cf4a69843941b9af10bf77 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 2 Jul 2020 16:46:31 -0700 Subject: [PATCH 203/236] A few light client typos --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/validator.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index b44d128d3b..e7ad5913a8 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -559,7 +559,7 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - ```python def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: """ - Return the light client committee of no more than ``TARGET_COMMITTEE_SIZE`` validators. + Return the light client committee of no more than ``LIGHT_CLIENT_COMMITTEE_SIZE`` validators. """ source_epoch = compute_committee_source_epoch(epoch, LIGHT_CLIENT_COMMITTEE_PERIOD) active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index c4a6abb176..b89b90c8a7 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -40,7 +40,7 @@ - [`SignedAggregateAndProof`](#signedaggregateandproof) - [Light client committee](#light-client-committee) - [Preparation](#preparation) - - [Light clent vote](#light-clent-vote) + - [Light client vote](#light-client-vote) - [Light client vote data](#light-client-vote-data) - [`LightClientVoteData`](#lightclientvotedata) - [Construct vote](#construct-vote) @@ -390,11 +390,11 @@ def is_in_next_light_client_committee(state: BeaconState, index: ValidatorIndex) return index in next_committee ``` -#### Light clent vote +#### Light client vote During a period of epochs that the validator is a part of the light client committee (`validator_index in get_light_client_committee(state, epoch)`), the validator creates and broadcasts a `LightClientVote` at each slot. -A validator should create and broadcast the `light_client_vote` to the `light_client_votes` pubsub topic when either (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) two-thirds of the `slot` have transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_. +A validator should create and broadcast the `light_client_vote` to the `light_client_votes` pubsub topic when either (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the `slot` have transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_. - Let `light_client_committee = get_light_client_committee(state, compute_epoch_at_slot(slot))` From 30e0438d490f3173fd2e107d2bc37422c5da47fe Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Fri, 3 Jul 2020 10:47:17 +0200 Subject: [PATCH 204/236] Update faq for tls we're not using tls1.3 (yet?) --- specs/phase0/p2p-interface.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 9949db66f3..76072c8b3c 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -64,7 +64,7 @@ It consists of four main sections: - [What is the difference between connection-level and stream-level protocol negotiation?](#what-is-the-difference-between-connection-level-and-stream-level-protocol-negotiation) - [Encryption](#encryption) - [Why are we not supporting SecIO?](#why-are-we-not-supporting-secio) - - [Why are we using Noise/TLS 1.3?](#why-are-we-using-noisetls-13) + - [Why are we using Noise?](#why-are-we-using-noise) - [Why are we using encryption at all?](#why-are-we-using-encryption-at-all) - [Gossipsub](#gossipsub) - [Why are we using a pub/sub algorithm for block and attestation propagation?](#why-are-we-using-a-pubsub-algorithm-for-block-and-attestation-propagation) @@ -778,6 +778,10 @@ Clients can adopt new transports without breaking old ones, and the multi-transp The QUIC standard is still not finalized (at working draft 22 at the time of writing), and not all mainstream runtimes/languages have mature, standard, and/or fully-interoperable [QUIC support](https://github.com/quicwg/base-drafts/wiki/Implementations). One remarkable example is node.js, where the QUIC implementation is [in early development](https://github.com/nodejs/quic). +*Note*: [TLS 1.3 is a prerequisite of the QUIC transport](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7), although an experiment exists to integrate Noise as the QUIC crypto layer: [nQUIC](https://eprint.iacr.org/2019/028). + +On the other hand, TLS 1.3 is the newest, simplified iteration of TLS. Old, insecure, obsolete ciphers and algorithms have been removed, adopting Ed25519 as the sole ECDH key agreement function. Handshakes are faster, 1-RTT data is supported, and session resumption is a reality, amongst other features. + ## Multiplexing ### Why are we using mplex/yamux? @@ -826,7 +830,7 @@ Although SecIO has wide language support, we won’t be using it for mainnet bec SecIO is not considered secure for the purposes of this spec. -### Why are we using Noise/TLS 1.3? +### Why are we using Noise? Copied from the Noise Protocol Framework [website](http://www.noiseprotocol.org): @@ -836,10 +840,6 @@ Noise in itself does not specify a single handshake procedure, but provides a fr Noise handshakes are lightweight and simple to understand, and are used in major cryptographic-centric projects like WireGuard, I2P, and Lightning. [Various](https://www.wireguard.com/papers/kobeissi-bhargavan-noise-explorer-2018.pdf) [studies](https://eprint.iacr.org/2019/436.pdf) have assessed the stated security goals of several Noise handshakes with positive results. -On the other hand, TLS 1.3 is the newest, simplified iteration of TLS. Old, insecure, obsolete ciphers and algorithms have been removed, adopting Ed25519 as the sole ECDH key agreement function. Handshakes are faster, 1-RTT data is supported, and session resumption is a reality, amongst other features. - -*Note*: [TLS 1.3 is a prerequisite of the QUIC transport](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7), although an experiment exists to integrate Noise as the QUIC crypto layer: [nQUIC](https://eprint.iacr.org/2019/028). - ### Why are we using encryption at all? Transport level encryption secures message exchange and provides properties that are useful for privacy, safety, and censorship resistance. These properties are derived from the following security guarantees that apply to the entire communication between two peers: From 04a6c96cdfcd931d78e728c109d44fdfcf870bb6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 3 Jul 2020 07:50:30 -0600 Subject: [PATCH 205/236] break p2p topics into separate md headers for better linking --- specs/phase0/p2p-interface.md | 102 +++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index a213d40273..db2fb86ea9 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -26,7 +26,13 @@ It consists of four main sections: - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof) + - [`voluntary_exit`](#voluntary_exit) + - [`proposer_slashing`](#proposer_slashing) + - [`attester_slashing`](#attester_slashing) - [Attestation subnets](#attestation-subnets) + - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) - [Attestations and Aggregation](#attestations-and-aggregation) - [Encodings](#encodings) - [The Req/Resp domain](#the-reqresp-domain) @@ -237,50 +243,68 @@ We utilize `ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are #### Global topics -There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `Name`s are: - -- `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network - - _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot). - - _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). - - _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. - - _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. - - _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. -- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`) - - _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). - - _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). - - _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. - - _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. - - _[REJECT]_ The attestation has participants -- that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. - - _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. - - _[REJECT]_ The aggregator's validator index is within the committee -- i.e. `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, aggregate.data.index)`. - - _[REJECT]_ The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. - - _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, is valid. - - _[REJECT]_ The signature of `aggregate` is valid. - -Additional global topics are used to propagate lower frequency validator messages. Their `Name`s are: - -- `voluntary_exit` - This topic is used solely for propagating signed voluntary validator exits to proposers on the network. Signed voluntary exits are sent in their entirety. The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network - - _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`. - - _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation. -- `proposer_slashing` - This topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. The following validations MUST pass before forwarding the `proposer_slashing` on to the network - - _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. - - _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation. -- `attester_slashing` - This topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. - - _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation has not yet been seen in any prior `attester_slashing` (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). - - _[REJECT]_ All of the conditions within `process_attester_slashing` pass validation. +There are two primary global topics used to propagate beacon blocks (`beacon_block`) and aggregate attestations (`beacon_aggregate_and_proof`) to all nodes on the network. +In addition to these two frequently used topics are three global topics used to propagate lower frequency validator messages (`voluntary_exit`, `proposer_slashing`, and `attester_slashing`). +##### `beacon_block` + +The `beacon_block` topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. +The following validations MUST pass before forwarding the `signed_beacon_block` on the network +- _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot). +- _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). +- _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. +- _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. +- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. + +##### `beacon_aggregate_and_proof` + +The `beacon_aggregate_and_proof` topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. +The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`) +- _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). +- _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). +- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. +- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. +- _[REJECT]_ The attestation has participants -- that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. +- _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. +- _[REJECT]_ The aggregator's validator index is within the committee -- i.e. `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, aggregate.data.index)`. +- _[REJECT]_ The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. +- _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, is valid. +- _[REJECT]_ The signature of `aggregate` is valid. + +##### `voluntary_exit` + +The `voluntary_exit` topic is used solely for propagating signed voluntary validator exits to proposers on the network. Signed voluntary exits are sent in their entirety. +The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network +- _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`. +- _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation. + +##### `proposer_slashing` + +The `proposer_slashing` topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. +The following validations MUST pass before forwarding the `proposer_slashing` on to the network +- _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. +- _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation. + +##### `attester_slashing` + +The `attester_slashing` topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. +- _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation has not yet been seen in any prior `attester_slashing` (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). +- _[REJECT]_ All of the conditions within `process_attester_slashing` pass validation. #### Attestation subnets -Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are: +Attestation subnets are used to propagate unaggregated attestations to subsections of the network. + +##### `beacon_attestation_{subnet_id}` -- `beacon_attestation_{subnet_id}` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet. - - _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be pre-computed along with the committee information for the signature check. - - _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). - - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. - - _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. - - _[REJECT]_ The signature of `attestation` is valid. +The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. +The following validations MUST pass before forwarding the `attestation` on the subnet. +- _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be pre-computed along with the committee information for the signature check. +- _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). +- _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). +- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. +- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. +- _[REJECT]_ The signature of `attestation` is valid. #### Attestations and Aggregation From 6195e027f115c4bdb7e7af918822dc23ea4f49e8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 3 Jul 2020 08:18:35 -0600 Subject: [PATCH 206/236] working through new-lines --- specs/phase0/p2p-interface.md | 782 ++++++++++++++++++++++++---------- 1 file changed, 559 insertions(+), 223 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index db2fb86ea9..f0c3c80b62 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -116,15 +116,21 @@ This section outlines the specification for the networking stack in Ethereum 2.0 ## Transport -Even though libp2p is a multi-transport stack (designed to listen on multiple simultaneous transports and endpoints transparently), we hereby define a profile for basic interoperability. +Even though libp2p is a multi-transport stack (designed to listen on multiple simultaneous transports and endpoints transparently), +we hereby define a profile for basic interoperability. -All implementations MUST support the TCP libp2p transport, and it MUST be enabled for both dialing and listening (i.e. outbound and inbound connections). The libp2p TCP transport supports listening on IPv4 and IPv6 addresses (and on multiple simultaneously). +All implementations MUST support the TCP libp2p transport, and it MUST be enabled for both dialing and listening (i.e. outbound and inbound connections). +The libp2p TCP transport supports listening on IPv4 and IPv6 addresses (and on multiple simultaneously). -Clients must support listening on at least one of IPv4 or IPv6. Clients that do _not_ have support for listening on IPv4 SHOULD be cognizant of the potential disadvantages in terms of Internet-wide routability/support. Clients MAY choose to listen only on IPv6, but MUST be capable of dialing both IPv4 and IPv6 addresses. +Clients must support listening on at least one of IPv4 or IPv6. +Clients that do _not_ have support for listening on IPv4 SHOULD be cognizant of the potential disadvantages in terms of +Internet-wide routability/support. Clients MAY choose to listen only on IPv6, but MUST be capable of dialing both IPv4 and IPv6 addresses. -All listening endpoints must be publicly dialable, and thus not rely on libp2p circuit relay, AutoNAT, or AutoRelay facilities. (Usage of circuit relay, AutoNAT, or AutoRelay will be specifically re-examined soon.) +All listening endpoints must be publicly dialable, and thus not rely on libp2p circuit relay, AutoNAT, or AutoRelay facilities. +(Usage of circuit relay, AutoNAT, or AutoRelay will be specifically re-examined soon.) -Nodes operating behind a NAT, or otherwise undialable by default (e.g. container runtime, firewall, etc.), MUST have their infrastructure configured to enable inbound traffic on the announced public listening endpoint. +Nodes operating behind a NAT, or otherwise undialable by default (e.g. container runtime, firewall, etc.), +MUST have their infrastructure configured to enable inbound traffic on the announced public listening endpoint. ## Encryption and identification @@ -137,15 +143,24 @@ As specified in the libp2p specification, clients MUST support the `XX` handshak Clients MUST use exact equality when negotiating protocol versions to use and MAY use the version to give priority to higher version numbers. -Clients MUST support [multistream-select 1.0](https://github.com/multiformats/multistream-select/) and MAY support [multiselect 2.0](https://github.com/libp2p/specs/pull/95) when the spec solidifies. Once all clients have implementations for multiselect 2.0, multistream-select 1.0 MAY be phased out. +Clients MUST support [multistream-select 1.0](https://github.com/multiformats/multistream-select/) +and MAY support [multiselect 2.0](https://github.com/libp2p/specs/pull/95) when the spec solidifies. +Once all clients have implementations for multiselect 2.0, multistream-select 1.0 MAY be phased out. ## Multiplexing -During connection bootstrapping, libp2p dynamically negotiates a mutually supported multiplexing method to conduct parallel conversations. This applies to transports that are natively incapable of multiplexing (e.g. TCP, WebSockets, WebRTC), and is omitted for capable transports (e.g. QUIC). +During connection bootstrapping, libp2p dynamically negotiates a mutually supported multiplexing method to conduct parallel conversations. +This applies to transports that are natively incapable of multiplexing (e.g. TCP, WebSockets, WebRTC), +and is omitted for capable transports (e.g. QUIC). -Two multiplexers are commonplace in libp2p implementations: [mplex](https://github.com/libp2p/specs/tree/master/mplex) and [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). Their protocol IDs are, respectively: `/mplex/6.7.0` and `/yamux/1.0.0`. +Two multiplexers are commonplace in libp2p implementations: +[mplex](https://github.com/libp2p/specs/tree/master/mplex) and [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). +Their protocol IDs are, respectively: `/mplex/6.7.0` and `/yamux/1.0.0`. -Clients MUST support [mplex](https://github.com/libp2p/specs/tree/master/mplex) and MAY support [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). If both are supported by the client, yamux MUST take precedence during negotiation. See the [Rationale](#design-decision-rationale) section below for tradeoffs. +Clients MUST support [mplex](https://github.com/libp2p/specs/tree/master/mplex) +and MAY support [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). +If both are supported by the client, yamux MUST take precedence during negotiation. +See the [Rationale](#design-decision-rationale) section below for tradeoffs. # Eth2 network interaction domains @@ -176,14 +191,18 @@ Clients MUST locally store the following `MetaData`: Where -- `seq_number` is a `uint64` starting at `0` used to version the node's metadata. If any other field in the local `MetaData` changes, the node MUST increment `seq_number` by 1. +- `seq_number` is a `uint64` starting at `0` used to version the node's metadata. + If any other field in the local `MetaData` changes, the node MUST increment `seq_number` by 1. - `attnets` is a `Bitvector` representing the node's persistent attestation subnet subscriptions. -*Note*: `MetaData.seq_number` is used for versioning of the node's metadata, is entirely independent of the ENR sequence number, and will in most cases be out of sync with the ENR sequence number. +*Note*: `MetaData.seq_number` is used for versioning of the node's metadata, +is entirely independent of the ENR sequence number, +and will in most cases be out of sync with the ENR sequence number. ## The gossip domain: gossipsub -Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) libp2p protocol including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) extension. +Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) libp2p Protocol +including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) extension. **Protocol ID:** `/meshsub/1.1.0` @@ -204,17 +223,23 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master ### Topics and messages -Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkDigestValue/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded. +Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). +Topic strings have form: `/eth2/ForkDigestValue/Name/Encoding`. +This defines both the type of data being sent on the topic and how the data field of the message is encoded. - `ForkDigestValue` - the lowercase hex-encoded (no "0x" prefix) bytes of `compute_fork_digest(current_fork_version, genesis_validators_root)` where - `current_fork_version` is the fork version of the epoch of the message to be sent on the topic - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - `Name` - see table below -- `Encoding` - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. See the [Encodings](#Encodings) section for further details. +- `Encoding` - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. + See the [Encodings](#Encodings) section for further details. -*Note*: `ForkDigestValue` is composed of values that are not known until the genesis block/state are available. Due to this, clients SHOULD NOT subscribe to gossipsub topics until these genesis values are known. +*Note*: `ForkDigestValue` is composed of values that are not known until the genesis block/state are available. +Due to this, clients SHOULD NOT subscribe to gossipsub topics until these genesis values are known. -Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. +Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. +Clients MUST reject (fail validation) messages that are over this size limit. +Likewise, clients MUST NOT emit or propagate messages larger than this limit. The `message-id` of a gossipsub message MUST be: @@ -238,57 +263,95 @@ Clients MUST reject (fail validation) messages containing an incorrect type, or When processing incoming gossip, clients MAY descore or disconnect peers who fail to observe these constraints. -Gossipsub v1.1 introduces [Extended Validators](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators) for the application to aid in the gossipsub peer-scoring scheme. -We utilize `ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are application specific validations. If all validations pass, return `ACCEPT`. If one or more validations fail while processing the items in order, return either `REJECT` or `IGNORE` as specified in the prefix of the particular condition. +Gossipsub v1.1 introduces [Extended Validators](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators) +for the application to aid in the gossipsub peer-scoring scheme. +We utilize `ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are application specific validations. +If all validations pass, return `ACCEPT`. +If one or more validations fail while processing the items in order, return either `REJECT` or `IGNORE` as specified in the prefix of the particular condition. #### Global topics -There are two primary global topics used to propagate beacon blocks (`beacon_block`) and aggregate attestations (`beacon_aggregate_and_proof`) to all nodes on the network. -In addition to these two frequently used topics are three global topics used to propagate lower frequency validator messages (`voluntary_exit`, `proposer_slashing`, and `attester_slashing`). +There are two primary global topics used to propagate beacon blocks (`beacon_block`) +and aggregate attestations (`beacon_aggregate_and_proof`) to all nodes on the network. + +There are three additional global topics are used to propagate lower frequency validator messages +(`voluntary_exit`, `proposer_slashing`, and `attester_slashing`). ##### `beacon_block` -The `beacon_block` topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. -The following validations MUST pass before forwarding the `signed_beacon_block` on the network -- _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot). -- _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). +The `beacon_block` topic is used solely for propagating new signed beacon blocks to all nodes on the networks. +Signed blocks are sent in their entirety. + +The following validations MUST pass before forwarding the `signed_beacon_block` on the network. +- _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot). +- _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- + i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` + (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). - _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. - _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. -- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. +- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot + in the context of the current shuffling (defined by `parent_root`/`slot`). + If the `proposer_index` cannot immediately be verified against the expected shuffling, + the block MAY be queued for later processing while proposers for the block's branch are calculated -- + in such a case _do not_ `REJECT`, instead `IGNORE` this message. ##### `beacon_aggregate_and_proof` -The `beacon_aggregate_and_proof` topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. -The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. (We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`) -- _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). -- _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). -- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. +The `beacon_aggregate_and_proof` topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) +to subscribing nodes (typically validators) to be included in future blocks. + +The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. +(We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`) +- _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` + (a client MAY queue future aggregates for processing at the appropriate slot). +- _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen + (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). +- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator + with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. - _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. -- _[REJECT]_ The attestation has participants -- that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. -- _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. -- _[REJECT]_ The aggregator's validator index is within the committee -- i.e. `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, aggregate.data.index)`. -- _[REJECT]_ The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. +- _[REJECT]_ The attestation has participants -- + that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. +- _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- + i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. +- _[REJECT]_ The aggregator's validator index is within the committee -- + i.e. `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, aggregate.data.index)`. +- _[REJECT]_ The `aggregate_and_proof.selection_proof` is a valid signature + of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. - _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, is valid. - _[REJECT]_ The signature of `aggregate` is valid. ##### `voluntary_exit` -The `voluntary_exit` topic is used solely for propagating signed voluntary validator exits to proposers on the network. Signed voluntary exits are sent in their entirety. -The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network -- _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`. +The `voluntary_exit` topic is used solely for propagating signed voluntary validator exits to proposers on the network. +Signed voluntary exits are sent in their entirety. + +The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network. +- _[IGNORE]_ The voluntary exit is the first valid voluntary exit received + for the validator with index `signed_voluntary_exit.message.validator_index`. - _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation. ##### `proposer_slashing` -The `proposer_slashing` topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. -The following validations MUST pass before forwarding the `proposer_slashing` on to the network -- _[IGNORE]_ The proposer slashing is the first valid proposer slashing received for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. +The `proposer_slashing` topic is used solely for propagating proposer slashings to proposers on the network. +Proposer slashings are sent in their entirety. + +The following validations MUST pass before forwarding the `proposer_slashing` on to the network. +- _[IGNORE]_ The proposer slashing is the first valid proposer slashing received + for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. - _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation. ##### `attester_slashing` -The `attester_slashing` topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. -- _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation has not yet been seen in any prior `attester_slashing` (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). +The `attester_slashing` topic is used solely for propagating attester slashings to proposers on the network. +Attester slashings are sent in their entirety. + +Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. +- _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation + has not yet been seen in any prior `attester_slashing` + (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, + verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). - _[REJECT]_ All of the conditions within `process_attester_slashing` pass validation. #### Attestation subnets @@ -297,24 +360,35 @@ Attestation subnets are used to propagate unaggregated attestations to subsectio ##### `beacon_attestation_{subnet_id}` -The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. +The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated attestations +to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. + The following validations MUST pass before forwarding the `attestation` on the subnet. -- _[REJECT]_ The attestation is for the correct subnet -- i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, which may be pre-computed along with the committee information for the signature check. -- _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). -- _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). -- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. +- _[REJECT]_ The attestation is for the correct subnet -- + i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, + where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, + which may be pre-computed along with the committee information for the signature check. +- _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots + (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- + i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` + (a client MAY queue future attestations for processing at the appropriate slot). +- _[REJECT]_ The attestation is unaggregated -- + that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). +- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet + that has an identical `attestation.data.target.epoch` and participating validator index. - _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. - _[REJECT]_ The signature of `attestation` is valid. #### Attestations and Aggregation -Attestation broadcasting is grouped into subnets defined by a topic. The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. +Attestation broadcasting is grouped into subnets defined by a topic. +The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. `beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1. The subnets are rotated through with `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` subnets per slot. -Unaggregated attestations are sent to the subnet topic, `beacon_attestation_{compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)}` as `Attestation`s. - +Unaggregated attestations are sent as `Attestation`s to the subnet topic, +`beacon_attestation_{compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)}` as `Attestation`s. Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `AggregateAndProof`s. @@ -322,11 +396,17 @@ Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `A Topics are post-fixed with an encoding. Encodings define how the payload of a gossipsub message is encoded. -- `ssz_snappy` - All objects are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) block compression. Example: The beacon aggregate attestation topic string is `/eth2/446a7232/beacon_aggregate_and_proof/ssz_snappy`, the fork digest is `446a7232` and the data field of a gossipsub message is an `AggregateAndProof` that has been SSZ-encoded and then compressed with Snappy. +- `ssz_snappy` - All objects are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) block compression. + Example: The beacon aggregate attestation topic string is `/eth2/446a7232/beacon_aggregate_and_proof/ssz_snappy`, + the fork digest is `446a7232` and the data field of a gossipsub message is an `AggregateAndProof` + that has been SSZ-encoded and then compressed with Snappy. -Snappy has two formats: "block" and "frames" (streaming). Gossip messages remain relatively small (100s of bytes to 100s of kilobytes) so [basic snappy block compression](https://github.com/google/snappy/blob/master/format_description.txt) is used to avoid the additional overhead associated with snappy frames. +Snappy has two formats: "block" and "frames" (streaming). +Gossip messages remain relatively small (100s of bytes to 100s of kilobytes) +so [basic snappy block compression](https://github.com/google/snappy/blob/master/format_description.txt) is used to avoid the additional overhead associated with snappy frames. -Implementations MUST use a single encoding for gossip. Changing an encoding will require coordination between participating implementations. +Implementations MUST use a single encoding for gossip. +Changing an encoding will require coordination between participating implementations. ## The Req/Resp domain @@ -340,16 +420,22 @@ Each message type is segregated into its own libp2p protocol ID, which is a case With: -- `ProtocolPrefix` - messages are grouped into families identified by a shared libp2p protocol name prefix. In this case, we use `/eth2/beacon_chain/req`. +- `ProtocolPrefix` - messages are grouped into families identified by a shared libp2p protocol name prefix. + In this case, we use `/eth2/beacon_chain/req`. - `MessageName` - each request is identified by a name consisting of English alphabet, digits and underscores (`_`). -- `SchemaVersion` - an ordinal version number (e.g. 1, 2, 3…). Each schema is versioned to facilitate backward and forward-compatibility when possible. -- `Encoding` - while the schema defines the data types in more abstract terms, the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. See the [Encodings](#Encoding-strategies) section for further details. +- `SchemaVersion` - an ordinal version number (e.g. 1, 2, 3…). + Each schema is versioned to facilitate backward and forward-compatibility when possible. +- `Encoding` - while the schema defines the data types in more abstract terms, + the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. + See the [Encodings](#Encoding-strategies) section for further details. -This protocol segregation allows libp2p `multistream-select 1.0` / `multiselect 2.0` to handle the request type, version, and encoding negotiation before establishing the underlying streams. +This protocol segregation allows libp2p `multistream-select 1.0` / `multiselect 2.0` +to handle the request type, version, and encoding negotiation before establishing the underlying streams. ### Req/Resp interaction -We use ONE stream PER request/response interaction. Streams are closed when the interaction finishes, whether in success or in error. +We use ONE stream PER request/response interaction. +Streams are closed when the interaction finishes, whether in success or in error. Request/response messages MUST adhere to the encoding specified in the protocol name and follow this structure (relaxed BNF grammar): @@ -360,22 +446,32 @@ response_chunk ::= | | result ::= “0” | “1” | “2” | [“128” ... ”255”] ``` -The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. +The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. +Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; +however, certain encodings like SSZ do, for added security. -A `response` is formed by zero or more `response_chunk`s. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. +A `response` is formed by zero or more `response_chunk`s. +Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. +All other response types (non-Lists) send a single `response_chunk`. -For both `request`s and `response`s, the `encoding-dependent-header` MUST be valid, and the `encoded-payload` must be valid within the constraints of the `encoding-dependent-header`. -This includes type-specific bounds on payload size for some encoding strategies. Regardless of these type specific bounds, a global maximum uncompressed byte size of `MAX_CHUNK_SIZE` MUST be applied to all method response chunks. +For both `request`s and `response`s, the `encoding-dependent-header` MUST be valid, +and the `encoded-payload` must be valid within the constraints of the `encoding-dependent-header`. +This includes type-specific bounds on payload size for some encoding strategies. +Regardless of these type specific bounds, a global maximum uncompressed byte size of `MAX_CHUNK_SIZE` MUST be applied to all method response chunks. -Clients MUST ensure that lengths are within these bounds; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. +Clients MUST ensure that lengths are within these bounds; if not, they SHOULD reset the stream immediately. +Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. #### Requesting side -Once a new stream with the protocol ID for the request type has been negotiated, the full request message SHOULD be sent immediately. The request MUST be encoded according to the encoding strategy. +Once a new stream with the protocol ID for the request type has been negotiated, the full request message SHOULD be sent immediately. +The request MUST be encoded according to the encoding strategy. -The requester MUST close the write side of the stream once it finishes writing the request message. At this point, the stream will be half-closed. +The requester MUST close the write side of the stream once it finishes writing the request message. +At this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). On that happening, the requester allows a further `RESP_TIMEOUT` for each subsequent `response_chunk` received. +The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). +On that happening, the requester allows a further `RESP_TIMEOUT` for each subsequent `response_chunk` received. If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. @@ -385,32 +481,41 @@ A requester SHOULD read from the stream until either: 3. Any part of the `response_chunk` fails validation. 4. The maximum number of requested chunks are read. -For requests consisting of a single valid `response_chunk`, the requester SHOULD read the chunk fully, as defined by the `encoding-dependent-header`, before closing the stream. +For requests consisting of a single valid `response_chunk`, +the requester SHOULD read the chunk fully, as defined by the `encoding-dependent-header`, before closing the stream. #### Responding side -Once a new stream with the protocol ID for the request type has been negotiated, the responder SHOULD process the incoming request and MUST validate it before processing it. +Once a new stream with the protocol ID for the request type has been negotiated, +the responder SHOULD process the incoming request and MUST validate it before processing it. Request processing and validation MUST be done according to the encoding strategy, until EOF (denoting stream half-closure by the requester). The responder MUST: 1. Use the encoding strategy to read the optional header. -2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure. +2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). + Should this not be the case, it should be treated as a failure. 3. Deserialize the expected type, and process the request. 4. Write the response which may consist of zero or more `response_chunk`s (result, optional header, payload). 5. Close their write side of the stream. At this point, the stream will be fully closed. -If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. +If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. +Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. -The entire request should be read in no more than `RESP_TIMEOUT`. Upon a timeout, the responder SHOULD reset the stream. +The entire request should be read in no more than `RESP_TIMEOUT`. +Upon a timeout, the responder SHOULD reset the stream. -The responder SHOULD send a `response_chunk` promptly. Chunks start with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). For multiple chunks, only the last chunk is allowed to have a non-zero error code (i.e. The chunk stream is terminated once an error occurs). +The responder SHOULD send a `response_chunk` promptly. +Chunks start with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). +For multiple chunks, only the last chunk is allowed to have a non-zero error code (i.e. The chunk stream is terminated once an error occurs). The response code can have one of the following values, encoded as a single unsigned byte: - 0: **Success** -- a normal response follows, with contents matching the expected message schema and encoding specified in the request. -- 1: **InvalidRequest** -- the contents of the request are semantically invalid, or the payload is malformed, or could not be understood. The response payload adheres to the `ErrorMessage` schema (described below). -- 2: **ServerError** -- the responder encountered an error while processing the request. The response payload adheres to the `ErrorMessage` schema (described below). +- 1: **InvalidRequest** -- the contents of the request are semantically invalid, or the payload is malformed, or could not be understood. + The response payload adheres to the `ErrorMessage` schema (described below). +- 2: **ServerError** -- the responder encountered an error while processing the request. + The response payload adheres to the `ErrorMessage` schema (described below). Clients MAY use response codes above `128` to indicate alternative, erroneous request-specific responses. @@ -424,27 +529,36 @@ The `ErrorMessage` schema is: ) ``` -*Note*: By convention, the `error_message` is a sequence of bytes that MAY be interpreted as a UTF-8 string (for debugging purposes). Clients MUST treat as valid any byte sequences. +*Note*: By convention, the `error_message` is a sequence of bytes that MAY be interpreted as a UTF-8 string (for debugging purposes). +Clients MUST treat as valid any byte sequences. ### Encoding strategies -The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time: +The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. +Two values are possible at this time: -- `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s. -- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) frames compression. This encoding type MUST be supported by all clients. +- `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md). + This encoding type MUST be supported by all clients. + For objects containing a single field, only the field is SSZ-encoded not a container with a single field. + For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s. +- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) frames compression. + This encoding type MUST be supported by all clients. #### SSZ-encoding strategy (with or without Snappy) The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded. -If the Snappy variant is selected, we feed the serialized form of the object to the Snappy compressor on encoding. The inverse happens on decoding. +If the Snappy variant is selected, we feed the serialized form of the object to the Snappy compressor on encoding. +The inverse happens on decoding. -Snappy has two formats: "block" and "frames" (streaming). To support large requests and response chunks, snappy-framing is used. +Snappy has two formats: "block" and "frames" (streaming). +To support large requests and response chunks, snappy-framing is used. Since snappy frame contents [have a maximum size of `65536` bytes](https://github.com/google/snappy/blob/master/framing_format.txt#L104) - and frame headers are just `identifier (1) + checksum (4)` bytes, the expected buffering of a single frame is acceptable. +and frame headers are just `identifier (1) + checksum (4)` bytes, the expected buffering of a single frame is acceptable. -**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST encode the length of the raw SSZ bytes, encoded as an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). +**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST encode the length of the raw SSZ bytes, +encoded as an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). *Writing*: By first computing and writing the SSZ byte length, the SSZ encoder can then directly write the chunk contents to the stream. If Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame. @@ -460,7 +574,8 @@ After reading a valid header, the payload MAY be read, while maintaining the siz A reader SHOULD NOT read more than `max_encoded_len(n)` bytes after reading the SSZ length-prefix `n` from the header. - For `ssz` this is: `n` -- For `ssz_snappy` this is: `32 + n + n // 6`. This is considered the [worst-case compression result](https://github.com/google/snappy/blob/537f4ad6240e586970fe554614542e9717df7902/snappy.cc#L98) by Snappy. +- For `ssz_snappy` this is: `32 + n + n // 6`. + This is considered the [worst-case compression result](https://github.com/google/snappy/blob/537f4ad6240e586970fe554614542e9717df7902/snappy.cc#L98) by Snappy. A reader SHOULD consider the following cases as invalid input: - Any remaining bytes, after having read the `n` SSZ bytes. An EOF is expected if more bytes are read than required. @@ -474,7 +589,8 @@ All messages that contain only a single field MUST be encoded directly as the ty Responses that are SSZ-lists (for example `List[SignedBeaconBlock, ...]`) send their constituents individually as `response_chunk`s. For example, the -`List[SignedBeaconBlock, ...]` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. +`List[SignedBeaconBlock, ...]` response type sends zero or more `response_chunk`s. +Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. ### Messages @@ -495,9 +611,11 @@ Request, Response Content: The fields are, as seen by the client at the time of sending the message: - `fork_digest`: The node's `ForkDigest` (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where - - `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) + - `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time + (not necessarily the epoch to which the node is sync) - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` -- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). +- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block + (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). - `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block. - `head_root`: The `hash_tree_root` root of the current head block (`BeaconBlock`). - `head_slot`: The slot of the block corresponding to the `head_root`. @@ -511,11 +629,16 @@ The response MUST consist of a single `response_chunk`. Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: 1. If `fork_digest` does not match the node's local `fork_digest`, since the client’s chain is on another fork. -2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, then Peer 1 would disconnect because it knows that their chains are irreparably disjoint. +2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. + For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, + then Peer 1 would disconnect because it knows that their chains are irreparably disjoint. -Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon blocks from its counterparty via the `BeaconBlocksByRange` request. +Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) +SHOULD request beacon blocks from its counterparty via the `BeaconBlocksByRange` request. -*Note*: Under abnormal network condition or after some rounds of `BeaconBlocksByRange` requests, the client might need to send `Status` request again to learn if the peer has a higher head. Implementers are free to implement such behavior in their own way. +*Note*: Under abnormal network condition or after some rounds of `BeaconBlocksByRange` requests, +the client might need to send `Status` request again to learn if the peer has a higher head. +Implementers are free to implement such behavior in their own way. #### Goodbye @@ -562,17 +685,21 @@ Response Content: ``` Requests beacon blocks in the slot range `[start_slot, start_slot + count * step)`, leading up to the current head block as selected by fork choice. -`step` defines the slot increment between blocks. For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at slots [2, 4, 6, …]. -In cases where a slot is empty for a given slot number, no block is returned. For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. +`step` defines the slot increment between blocks. +For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at slots [2, 4, 6, …]. +In cases where a slot is empty for a given slot number, no block is returned. +For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. A request MUST NOT have a 0 slot increment, i.e. `step >= 1`. `BeaconBlocksByRange` is primarily used to sync historical blocks. The request MUST be encoded as an SSZ-container. -The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. +The response MUST consist of zero or more `response_chunk`. +Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. -Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period and MUST support serving requests of blocks up to their own `head_block_root`. +Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period +and MUST support serving requests of blocks up to their own `head_block_root`. Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks. @@ -582,12 +709,16 @@ Clients MAY limit the number of blocks in the response. The response MUST contain no more than `count` blocks. -Clients MUST respond with blocks from their view of the current fork choice -- that is, blocks from the single chain defined by the current head. Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the `Status` handshake. +Clients MUST respond with blocks from their view of the current fork choice +-- that is, blocks from the single chain defined by the current head. +Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the `Status` handshake. Clients MUST respond with blocks that are consistent from a single chain within the context of the request. -This applies to any `step` value. In particular when `step == 1`, each `parent_root` MUST match the `hash_tree_root` of the preceding block. +This applies to any `step` value. +In particular when `step == 1`, each `parent_root` MUST match the `hash_tree_root` of the preceding block. -After the initial block, clients MAY stop in the process of responding if their fork choice changes the view of the chain in the context of the request. +After the initial block, clients MAY stop in the process of responding +if their fork choice changes the view of the chain in the context of the request. #### BeaconBlocksByRoot @@ -609,7 +740,9 @@ Response Content: ) ``` -Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). The response is a list of `SignedBeaconBlock` whose length is less than or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks. +Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). +The response is a list of `SignedBeaconBlock` whose length is less than or equal to the number of requested blocks. +It may be less in the case that the responding peer is missing blocks. No more than `MAX_REQUEST_BLOCKS` may be requested at a time. @@ -617,11 +750,13 @@ No more than `MAX_REQUEST_BLOCKS` may be requested at a time. The request MUST be encoded as an SSZ-field. -The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. +The response MUST consist of zero or more `response_chunk`. +Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. Clients MUST support requesting blocks since the latest finalized epoch. -Clients MUST respond with at least one block, if they have it. Clients MAY limit the number of blocks in the response. +Clients MUST respond with at least one block, if they have it. +Clients MAY limit the number of blocks in the response. #### Ping @@ -669,8 +804,9 @@ Response Content: ) ``` -Requests the MetaData of a peer. The request opens and negotiates the stream without -sending any request content. Once established the receiving peer responds with +Requests the MetaData of a peer. +The request opens and negotiates the stream without sending any request content. +Once established the receiving peer responds with it's local most up-to-date MetaData. The response MUST be encoded as an SSZ-container. @@ -681,21 +817,26 @@ The response MUST consist of a single `response_chunk`. Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery. -`discv5` is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only. `discv5` supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context. +`discv5` is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only. +`discv5` supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context. :warning: Under construction. :warning: ### Integration into libp2p stacks -`discv5` SHOULD be integrated into the client’s libp2p stack by implementing an adaptor to make it conform to the [service discovery](https://github.com/libp2p/go-libp2p-core/blob/master/discovery/discovery.go) and [peer routing](https://github.com/libp2p/go-libp2p-core/blob/master/routing/routing.go#L36-L44) abstractions and interfaces (go-libp2p links provided). +`discv5` SHOULD be integrated into the client’s libp2p stack by implementing an adaptor +to make it conform to the [service discovery](https://github.com/libp2p/go-libp2p-core/blob/master/discovery/discovery.go) +and [peer routing](https://github.com/libp2p/go-libp2p-core/blob/master/routing/routing.go#L36-L44) abstractions and interfaces (go-libp2p links provided). -Inputs to operations include peer IDs (when locating a specific peer), or capabilities (when searching for peers with a specific capability), and the outputs will be multiaddrs converted from the ENR records returned by the discv5 backend. +Inputs to operations include peer IDs (when locating a specific peer) or capabilities (when searching for peers with a specific capability), +and the outputs will be multiaddrs converted from the ENR records returned by the discv5 backend. This integration enables the libp2p stack to subsequently form connections and streams with discovered peers. ### ENR structure -The Ethereum Node Record (ENR) for an Ethereum 2.0 client MUST contain the following entries (exclusive of the sequence number and signature, which MUST be present in an ENR): +The Ethereum Node Record (ENR) for an Ethereum 2.0 client MUST contain the following entries +(exclusive of the sequence number and signature, which MUST be present in an ENR): - The compressed secp256k1 publickey, 33 bytes (`secp256k1` field). @@ -709,7 +850,8 @@ Specifications of these parameters can be found in the [ENR Specification](http: #### Attestation subnet bitfield -The ENR `attnets` entry signifies the attestation subnet bitfield with the following form to more easily discover peers participating in particular attestation gossip subnets. +The ENR `attnets` entry signifies the attestation subnet bitfield with the following form +to more easily discover peers participating in particular attestation gossip subnets. | Key | Value | |:-------------|:-------------------------------------------------| @@ -721,7 +863,8 @@ If a node's `MetaData.attnets` is composed of all zeros, the ENR MAY optionally #### `eth2` field -ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current fork digest, next fork version, and next fork epoch to ensure connections are made with peers on the intended eth2 network. +ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current fork digest, next fork version, +and next fork epoch to ensure connections are made with peers on the intended eth2 network. | Key | Value | |:-------------|:--------------------| @@ -740,26 +883,39 @@ Specifically, the value of the `eth2` key MUST be the following SSZ encoded obje where the fields of `ENRForkID` are defined as * `fork_digest` is `compute_fork_digest(current_fork_version, genesis_validators_root)` where - * `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) + * `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time + (not necessarily the epoch to which the node is sync) * `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` -* `next_fork_version` is the fork version corresponding to the next planned hard fork at a future epoch. If no future fork is planned, set `next_fork_version = current_fork_version` to signal this fact -* `next_fork_epoch` is the epoch at which the next fork is planned and the `current_fork_version` will be updated. If no future fork is planned, set `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact - -*Note*: `fork_digest` is composed of values that are not not known until the genesis block/state are available. Due to this, clients SHOULD NOT form ENRs and begin peer discovery until genesis values are known. One notable exception to this rule is the distribution of bootnode ENRs prior to genesis. In this case, bootnode ENRs SHOULD be initially distributed with `eth2` field set as `ENRForkID(fork_digest=compute_fork_digest(GENESIS_FORK_VERSION, b'\x00'*32), next_fork_version=GENESIS_FORK_VERSION, next_fork_epoch=FAR_FUTURE_EPOCH)`. After genesis values are known, the bootnodes SHOULD update ENRs to participate in normal discovery operations. +* `next_fork_version` is the fork version corresponding to the next planned hard fork at a future epoch. + If no future fork is planned, set `next_fork_version = current_fork_version` to signal this fact +* `next_fork_epoch` is the epoch at which the next fork is planned and the `current_fork_version` will be updated. + If no future fork is planned, set `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact + +*Note*: `fork_digest` is composed of values that are not not known until the genesis block/state are available. +Due to this, clients SHOULD NOT form ENRs and begin peer discovery until genesis values are known. +One notable exception to this rule is the distribution of bootnode ENRs prior to genesis. +In this case, bootnode ENRs SHOULD be initially distributed with `eth2` field set as +`ENRForkID(fork_digest=compute_fork_digest(GENESIS_FORK_VERSION, b'\x00'*32), next_fork_version=GENESIS_FORK_VERSION, next_fork_epoch=FAR_FUTURE_EPOCH)`. +After genesis values are known, the bootnodes SHOULD update ENRs to participate in normal discovery operations. Clients SHOULD connect to peers with `fork_digest`, `next_fork_version`, and `next_fork_epoch` that match local values. -Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. +Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. +Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, +these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. #### General capabilities -ENRs MUST include a structure enumerating the capabilities offered by the peer in an efficient manner. The concrete solution is currently undefined. Proposals include using namespaced bloom filters mapping capabilities to specific protocol IDs supported under that capability. +ENRs MUST include a structure enumerating the capabilities offered by the peer in an efficient manner. +The concrete solution is currently undefined. +Proposals include using namespaced bloom filters mapping capabilities to specific protocol IDs supported under that capability. ### Topic advertisement discv5's topic advertisement feature is not expected to be ready for mainnet launch of Phase 0. -Once this feature is built out and stable, we expect to use topic advertisement as a rendezvous facility for peers on shards. Until then, the ENR [attestation subnet bitfield](#attestation-subnet-bitfield) will be used for discovery of peers on particular subnets. +Once this feature is built out and stable, we expect to use topic advertisement as a rendezvous facility for peers on shards. +Until then, the ENR [attestation subnet bitfield](#attestation-subnet-bitfield) will be used for discovery of peers on particular subnets. # Design decision rationale @@ -767,7 +923,8 @@ Once this feature is built out and stable, we expect to use topic advertisement ### Why are we defining specific transports? -libp2p peers can listen on multiple transports concurrently, and these can change over time. Multiaddrs encode not only the address but also the transport to be used to dial. +libp2p peers can listen on multiple transports concurrently, and these can change over time. +Multiaddrs encode not only the address but also the transport to be used to dial. Due to this dynamic nature, agreeing on specific transports like TCP, QUIC, or WebSockets on paper becomes irrelevant. @@ -775,58 +932,88 @@ However, it is useful to define a minimum baseline for interoperability purposes ### Can clients support other transports/handshakes than the ones mandated by the spec? -Clients may support other transports such as libp2p QUIC, WebSockets, and WebRTC transports, if available in the language of choice. While interoperability shall not be harmed by lack of such support, the advantages are desirable: +Clients may support other transports such as libp2p QUIC, WebSockets, and WebRTC transports, if available in the language of choice. +While interoperability shall not be harmed by lack of such support, the advantages are desirable: - Better latency, performance, and other QoS characteristics (QUIC). - Paving the way for interfacing with future light clients (WebSockets, WebRTC). -The libp2p QUIC transport inherently relies on TLS 1.3 per requirement in section 7 of the [QUIC protocol specification](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7) and the accompanying [QUIC-TLS document](https://tools.ietf.org/html/draft-ietf-quic-tls-22). +The libp2p QUIC transport inherently relies on TLS 1.3 per requirement in section 7 +of the [QUIC protocol specification](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7) +and the accompanying [QUIC-TLS document](https://tools.ietf.org/html/draft-ietf-quic-tls-22). -The usage of one handshake procedure or the other shall be transparent to the Eth2 application layer, once the libp2p Host/Node object has been configured appropriately. +The usage of one handshake procedure or the other shall be transparent to the Eth2 application layer, +once the libp2p Host/Node object has been configured appropriately. ### What are the advantages of using TCP/QUIC/Websockets? -TCP is a reliable, ordered, full-duplex, congestion-controlled network protocol that powers much of the Internet as we know it today. HTTP/1.1 and HTTP/2 run atop TCP. +TCP is a reliable, ordered, full-duplex, congestion-controlled network protocol that powers much of the Internet as we know it today. +HTTP/1.1 and HTTP/2 run atop TCP. -QUIC is a new protocol that’s in the final stages of specification by the IETF QUIC WG. It emerged from Google’s SPDY experiment. The QUIC transport is undoubtedly promising. It’s UDP-based yet reliable, ordered, multiplexed, natively secure (TLS 1.3), reduces latency vs. TCP, and offers stream-level and connection-level congestion control (thus removing head-of-line blocking), 0-RTT connection establishment, and endpoint migration, amongst other features. UDP also has better NAT traversal properties than TCP—something we desperately pursue in peer-to-peer networks. +QUIC is a new protocol that’s in the final stages of specification by the IETF QUIC WG. +It emerged from Google’s SPDY experiment. The QUIC transport is undoubtedly promising. +It’s UDP-based yet reliable, ordered, multiplexed, natively secure (TLS 1.3), reduces latency vs. TCP, +and offers stream-level and connection-level congestion control (thus removing head-of-line blocking), +0-RTT connection establishment, and endpoint migration, amongst other features. +UDP also has better NAT traversal properties than TCP—something we desperately pursue in peer-to-peer networks. -QUIC is being adopted as the underlying protocol for HTTP/3. This has the potential to award us censorship resistance via deep packet inspection for free. Provided that we use the same port numbers and encryption mechanisms as HTTP/3, our traffic may be indistinguishable from standard web traffic, and we may only become subject to standard IP-based firewall filtering—something we can counteract via other mechanisms. +QUIC is being adopted as the underlying protocol for HTTP/3. +This has the potential to award us censorship resistance via deep packet inspection for free. +Provided that we use the same port numbers and encryption mechanisms as HTTP/3, our traffic may be indistinguishable from standard web traffic, +and we may only become subject to standard IP-based firewall filtering—something we can counteract via other mechanisms. -WebSockets and/or WebRTC transports are necessary for interaction with browsers, and will become increasingly important as we incorporate browser-based light clients to the Eth2 network. +WebSockets and/or WebRTC transports are necessary for interaction with browsers, +and will become increasingly important as we incorporate browser-based light clients to the Eth2 network. ### Why do we not just support a single transport? -Networks evolve. Hardcoding design decisions leads to ossification, preventing the evolution of networks alongside the state of the art. Introducing changes on an ossified protocol is very costly, and sometimes, downright impracticable without causing undesirable breakage. +Networks evolve. +Hardcoding design decisions leads to ossification, preventing the evolution of networks alongside the state of the art. +Introducing changes on an ossified protocol is very costly, and sometimes, downright impracticable without causing undesirable breakage. Modeling for upgradeability and dynamic transport selection from the get-go lays the foundation for a future-proof stack. -Clients can adopt new transports without breaking old ones, and the multi-transport ability enables constrained and sandboxed environments (e.g. browsers, embedded devices) to interact with the network as first-class citizens via suitable/native transports (e.g. WSS), without the need for proxying or trust delegation to servers. +Clients can adopt new transports without breaking old ones, and the multi-transport ability enables constrained and sandboxed environments +(e.g. browsers, embedded devices) to interact with the network as first-class citizens via suitable/native transports (e.g. WSS), +without the need for proxying or trust delegation to servers. ### Why are we not using QUIC from the start? -The QUIC standard is still not finalized (at working draft 22 at the time of writing), and not all mainstream runtimes/languages have mature, standard, and/or fully-interoperable [QUIC support](https://github.com/quicwg/base-drafts/wiki/Implementations). One remarkable example is node.js, where the QUIC implementation is [in early development](https://github.com/nodejs/quic). +The QUIC standard is still not finalized (at working draft 22 at the time of writing), +and not all mainstream runtimes/languages have mature, standard, and/or fully-interoperable [QUIC support](https://github.com/quicwg/base-drafts/wiki/Implementations). +One remarkable example is node.js, where the QUIC implementation is [in early development](https://github.com/nodejs/quic). -*Note*: [TLS 1.3 is a prerequisite of the QUIC transport](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7), although an experiment exists to integrate Noise as the QUIC crypto layer: [nQUIC](https://eprint.iacr.org/2019/028). +*Note*: [TLS 1.3 is a prerequisite of the QUIC transport](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7), +although an experiment exists to integrate Noise as the QUIC crypto layer: [nQUIC](https://eprint.iacr.org/2019/028). -On the other hand, TLS 1.3 is the newest, simplified iteration of TLS. Old, insecure, obsolete ciphers and algorithms have been removed, adopting Ed25519 as the sole ECDH key agreement function. Handshakes are faster, 1-RTT data is supported, and session resumption is a reality, amongst other features. +On the other hand, TLS 1.3 is the newest, simplified iteration of TLS. +Old, insecure, obsolete ciphers and algorithms have been removed, adopting Ed25519 as the sole ECDH key agreement function. +Handshakes are faster, 1-RTT data is supported, and session resumption is a reality, amongst other features. ## Multiplexing ### Why are we using mplex/yamux? -[Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md) is a multiplexer invented by Hashicorp that supports stream-level congestion control. Implementations exist in a limited set of languages, and it’s not a trivial piece to develop. +[Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md) is a multiplexer invented by Hashicorp that supports stream-level congestion control. +Implementations exist in a limited set of languages, and it’s not a trivial piece to develop. -Conscious of that, the libp2p community conceptualized [mplex](https://github.com/libp2p/specs/blob/master/mplex/README.md) as a simple, minimal multiplexer for usage with libp2p. It does not support stream-level congestion control and is subject to head-of-line blocking. +Conscious of that, the libp2p community conceptualized [mplex](https://github.com/libp2p/specs/blob/master/mplex/README.md) +as a simple, minimal multiplexer for usage with libp2p. +It does not support stream-level congestion control and is subject to head-of-line blocking. -Overlay multiplexers are not necessary with QUIC since the protocol provides native multiplexing, but they need to be layered atop TCP, WebSockets, and other transports that lack such support. +Overlay multiplexers are not necessary with QUIC since the protocol provides native multiplexing, +but they need to be layered atop TCP, WebSockets, and other transports that lack such support. ## Protocol Negotiation ### When is multiselect 2.0 due and why do we plan to migrate to it? -multiselect 2.0 is currently being conceptualized. The debate started [on this issue](https://github.com/libp2p/specs/pull/95), but it got overloaded—as it tends to happen with large conceptual OSS discussions that touch the heart and core of a system. +multiselect 2.0 is currently being conceptualized. +The debate started [on this issue](https://github.com/libp2p/specs/pull/95), +but it got overloaded—as it tends to happen with large conceptual OSS discussions that touch the heart and core of a system. -At some point in 2020, we expect a renewed initiative to first define the requirements, constraints, assumptions, and features, in order to lock in basic consensus upfront and subsequently build on that consensus by submitting a specification for implementation. +At some point in 2020, we expect a renewed initiative to first define the requirements, constraints, assumptions, and features, +in order to lock in basic consensus upfront and subsequently build on that consensus by submitting a specification for implementation. We plan to eventually migrate to multiselect 2.0 because it will: @@ -837,24 +1024,31 @@ We plan to eventually migrate to multiselect 2.0 because it will: ### What is the difference between connection-level and stream-level protocol negotiation? -All libp2p connections must be authenticated, encrypted, and multiplexed. Connections using network transports unsupportive of native authentication/encryption and multiplexing (e.g. TCP) need to undergo protocol negotiation to agree on a mutually supported: +All libp2p connections must be authenticated, encrypted, and multiplexed. +Connections using network transports unsupportive of native authentication/encryption and multiplexing (e.g. TCP) need to undergo protocol negotiation to agree on a mutually supported: 1. authentication/encryption mechanism (such as SecIO, TLS 1.3, Noise). 2. overlay multiplexer (such as mplex, Yamux, spdystream). -In this specification, we refer to these two as *connection-level negotiations*. Transports supporting those features natively (such as QUIC) omit those negotiations. +In this specification, we refer to these two as *connection-level negotiations*. +Transports supporting those features natively (such as QUIC) omit those negotiations. -After successfully selecting a multiplexer, all subsequent I/O happens over *streams*. When opening streams, peers pin a protocol to that stream, by conducting *stream-level protocol negotiation*. +After successfully selecting a multiplexer, all subsequent I/O happens over *streams*. +When opening streams, peers pin a protocol to that stream, by conducting *stream-level protocol negotiation*. -At present, multistream-select 1.0 is used for both types of negotiation, but multiselect 2.0 will use dedicated mechanisms for connection bootstrapping process and stream protocol negotiation. +At present, multistream-select 1.0 is used for both types of negotiation, +but multiselect 2.0 will use dedicated mechanisms for connection bootstrapping process and stream protocol negotiation. ## Encryption ### Why are we not supporting SecIO? -SecIO has been the default encryption layer for libp2p for years. It is used in IPFS and Filecoin. And although it will be superseded shortly, it is proven to work at scale. +SecIO has been the default encryption layer for libp2p for years. +It is used in IPFS and Filecoin. And although it will be superseded shortly, it is proven to work at scale. -Although SecIO has wide language support, we won’t be using it for mainnet because, amongst other things, it requires several round trips to be sound, and doesn’t support early data (0-RTT data), a mechanism that multiselect 2.0 will leverage to reduce round trips during connection bootstrapping. +Although SecIO has wide language support, we won’t be using it for mainnet because, amongst other things, +it requires several round trips to be sound, and doesn’t support early data (0-RTT data), +a mechanism that multiselect 2.0 will leverage to reduce round trips during connection bootstrapping. SecIO is not considered secure for the purposes of this spec. @@ -862,29 +1056,41 @@ SecIO is not considered secure for the purposes of this spec. Copied from the Noise Protocol Framework [website](http://www.noiseprotocol.org): -> Noise is a framework for building crypto protocols. Noise protocols support mutual and optional authentication, identity hiding, forward secrecy, zero round-trip encryption, and other advanced features. +> Noise is a framework for building crypto protocols. +Noise protocols support mutual and optional authentication, identity hiding, forward secrecy, zero round-trip encryption, and other advanced features. -Noise in itself does not specify a single handshake procedure, but provides a framework to build secure handshakes based on Diffie-Hellman key agreement with a variety of tradeoffs and guarantees. +Noise in itself does not specify a single handshake procedure, +but provides a framework to build secure handshakes based on Diffie-Hellman key agreement with a variety of tradeoffs and guarantees. -Noise handshakes are lightweight and simple to understand, and are used in major cryptographic-centric projects like WireGuard, I2P, and Lightning. [Various](https://www.wireguard.com/papers/kobeissi-bhargavan-noise-explorer-2018.pdf) [studies](https://eprint.iacr.org/2019/436.pdf) have assessed the stated security goals of several Noise handshakes with positive results. +Noise handshakes are lightweight and simple to understand, +and are used in major cryptographic-centric projects like WireGuard, I2P, and Lightning. +[Various](https://www.wireguard.com/papers/kobeissi-bhargavan-noise-explorer-2018.pdf) [studies](https://eprint.iacr.org/2019/436.pdf) +have assessed the stated security goals of several Noise handshakes with positive results. ### Why are we using encryption at all? -Transport level encryption secures message exchange and provides properties that are useful for privacy, safety, and censorship resistance. These properties are derived from the following security guarantees that apply to the entire communication between two peers: +Transport level encryption secures message exchange and provides properties that are useful for privacy, safety, and censorship resistance. +These properties are derived from the following security guarantees that apply to the entire communication between two peers: -- Peer authentication: the peer I’m talking to is really who they claim to be and who I expect them to be. -- Confidentiality: no observer can eavesdrop on the content of our messages. -- Integrity: the data has not been tampered with by a third-party while in transit. -- Non-repudiation: the originating peer cannot dispute that they sent the message. -- Depending on the chosen algorithms and mechanisms (e.g. continuous HMAC), we may obtain additional guarantees, such as non-replayability (this byte could’ve only been sent *now;* e.g. by using continuous HMACs), or perfect forward secrecy (in the case that a peer key is compromised, the content of a past conversation will not be compromised). +- Peer authentication: the peer I’m talking to is really who they claim to be and who I expect them to be. +- Confidentiality: no observer can eavesdrop on the content of our messages. +- Integrity: the data has not been tampered with by a third-party while in transit. +- Non-repudiation: the originating peer cannot dispute that they sent the message. +- Depending on the chosen algorithms and mechanisms (e.g. continuous HMAC), we may obtain additional guarantees, + such as non-replayability (this byte could’ve only been sent *now;* e.g. by using continuous HMACs), + or perfect forward secrecy (in the case that a peer key is compromised, the content of a past conversation will not be compromised). -Note that transport-level encryption is not exclusive of application-level encryption or cryptography. Transport-level encryption secures the communication itself, while application-level cryptography is necessary for the application’s use cases (e.g. signatures, randomness, etc.). +Note that transport-level encryption is not exclusive of application-level encryption or cryptography. +Transport-level encryption secures the communication itself, +while application-level cryptography is necessary for the application’s use cases (e.g. signatures, randomness, etc.). ## Gossipsub ### Why are we using a pub/sub algorithm for block and attestation propagation? -Pubsub is a technique to broadcast/disseminate data across a network rapidly. Such data is packaged in fire-and-forget messages that do not require a response from every recipient. Peers subscribed to a topic participate in the propagation of messages in that topic. +Pubsub is a technique to broadcast/disseminate data across a network rapidly. +Such data is packaged in fire-and-forget messages that do not require a response from every recipient. +Peers subscribed to a topic participate in the propagation of messages in that topic. The alternative is to maintain a fully connected mesh (all peers connected to each other 1:1), which scales poorly (O(n^2)). @@ -896,81 +1102,126 @@ For future extensibility with almost zero overhead now (besides the extra bytes Changing gossipsub/broadcasts requires a coordinated upgrade where all clients start publishing to the new topic together, during a hard fork. -When a node is preparing for upcoming tasks (e.g. validator duty lookahead) on a gossipsub topic, the node should join the topic of the future epoch in which the task is to occur in addition to listening to the topics for the current epoch. +When a node is preparing for upcoming tasks (e.g. validator duty lookahead) on a gossipsub topic, +the node should join the topic of the future epoch in which the task is to occur in addition to listening to the topics for the current epoch. ### Why must all clients use the same gossip topic instead of one negotiated between each peer pair? -Supporting multiple topics/encodings would require the presence of relayers to translate between encodings and topics so as to avoid network fragmentation where participants have diverging views on the gossiped state, making the protocol more complicated and fragile. +Supporting multiple topics/encodings would require the presence of relayers to translate between encodings +and topics so as to avoid network fragmentation where participants have diverging views on the gossiped state, +making the protocol more complicated and fragile. -Gossip protocols typically remember what messages they've seen for a finite period of time-based on message identity—if you publish the same message again after that time has passed, it will be re-broadcast—adding a relay delay also makes this scenario more likely. +Gossip protocols typically remember what messages they've seen for a finite period of time-based on message identity +-- if you publish the same message again after that time has passed, +it will be re-broadcast—adding a relay delay also makes this scenario more likely. -One can imagine that in a complicated upgrade scenario, we might have peers publishing the same message on two topics/encodings, but the price here is pretty high in terms of overhead—both computational and networking—so we'd rather avoid that. +One can imagine that in a complicated upgrade scenario, we might have peers publishing the same message on two topics/encodings, +but the price here is pretty high in terms of overhead -- both computational and networking -- so we'd rather avoid that. It is permitted for clients to publish data on alternative topics as long as they also publish on the network-wide mandatory topic. ### Why are the topics strings and not hashes? -Topic names have a hierarchical structure. In the future, gossipsub may support wildcard subscriptions (e.g. subscribe to all children topics under a root prefix) by way of prefix matching. Enforcing hashes for topic names would preclude us from leveraging such features going forward. +Topic names have a hierarchical structure. +In the future, gossipsub may support wildcard subscriptions +(e.g. subscribe to all children topics under a root prefix) by way of prefix matching. +Enforcing hashes for topic names would preclude us from leveraging such features going forward. -No security or privacy guarantees are lost as a result of choosing plaintext topic names, since the domain is finite anyway, and calculating a digest's preimage would be trivial. +No security or privacy guarantees are lost as a result of choosing plaintext topic names, +since the domain is finite anyway, and calculating a digest's preimage would be trivial. -Furthermore, the Eth2 topic names are shorter than their digest equivalents (assuming SHA-256 hash), so hashing topics would bloat messages unnecessarily. +Furthermore, the Eth2 topic names are shorter than their digest equivalents (assuming SHA-256 hash), +so hashing topics would bloat messages unnecessarily. ### Why are we overriding the default libp2p pubsub `message-id`? -For our current purposes, there is no need to address messages based on source peer, and it seems likely we might even override the message `from` to obfuscate the peer. By overriding the default `message-id` to use content-addressing we can filter unnecessary duplicates before hitting the application layer. +For our current purposes, there is no need to address messages based on source peer, +and it seems likely we might even override the message `from` to obfuscate the peer. +By overriding the default `message-id` to use content-addressing we can filter unnecessary duplicates before hitting the application layer. Some examples of where messages could be duplicated: * A validator client connected to multiple beacon nodes publishing duplicate gossip messages -* Attestation aggregation strategies where clients partially aggregate attestations and propagate them. Partial aggregates could be duplicated +* Attestation aggregation strategies where clients partially aggregate attestations and propagate them. + Partial aggregates could be duplicated * Clients re-publishing seen messages ### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets? -For some gossip channels (e.g. those for Attestations and BeaconBlocks), there are designated ranges of slots during which particular messages can be sent, limiting messages gossiped to those that can be reasonably used in the consensus at the current time/slot. This is to reduce optionality in DoS attacks. +For some gossip channels (e.g. those for Attestations and BeaconBlocks), +there are designated ranges of slots during which particular messages can be sent, +limiting messages gossiped to those that can be reasonably used in the consensus at the current time/slot. +This is to reduce optionality in DoS attacks. -`MAXIMUM_GOSSIP_CLOCK_DISPARITY` provides some leeway in validating slot ranges to prevent the gossip network from becoming overly brittle with respect to clock disparity. For minimum and maximum allowable slot broadcast times, `MAXIMUM_GOSSIP_CLOCK_DISPARITY` MUST be subtracted and added respectively, marginally extending the valid range. Although messages can at times be eagerly gossiped to the network, the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot. +`MAXIMUM_GOSSIP_CLOCK_DISPARITY` provides some leeway in validating slot ranges to prevent the gossip network +from becoming overly brittle with respect to clock disparity. +For minimum and maximum allowable slot broadcast times, +`MAXIMUM_GOSSIP_CLOCK_DISPARITY` MUST be subtracted and added respectively, marginally extending the valid range. +Although messages can at times be eagerly gossiped to the network, +the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot. The value of this constant is currently a placeholder and will be tuned based on data observed in testnets. ### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets? -Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. The exact grouping will be dependent on more involved network tests. This constant allows for more flexibility in setting up the network topology for attestation aggregation (as aggregation should happen on each subnet). The value is currently set to to be equal `MAX_COMMITTEES_PER_SLOT` until network tests indicate otherwise. +Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. +The exact grouping will be dependent on more involved network tests. +This constant allows for more flexibility in setting up the network topology for attestation aggregation (as aggregation should happen on each subnet). +The value is currently set to to be equal `MAX_COMMITTEES_PER_SLOT` if/until network tests indicate otherwise. ### Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots? -Attestations can only be included on chain within an epoch's worth of slots so this is the natural cutoff. There is no utility to the chain to broadcast attestations older than one epoch, and because validators have a chance to make a new attestation each epoch, there is minimal utility to the fork choice to relay old attestations as a new latest message can soon be created by each validator. +Attestations can only be included on chain within an epoch's worth of slots so this is the natural cutoff. +There is no utility to the chain to broadcast attestations older than one epoch, +and because validators have a chance to make a new attestation each epoch, +there is minimal utility to the fork choice to relay old attestations as a new latest message can soon be created by each validator. -In addition to this, relaying attestations requires validating the attestation in the context of the `state` during which it was created. Thus, validating arbitrarily old attestations would put additional requirements on which states need to be readily available to the node. This would result in a higher resource burden and could serve as a DoS vector. +In addition to this, relaying attestations requires validating the attestation in the context of the `state` during which it was created. +Thus, validating arbitrarily old attestations would put additional requirements on which states need to be readily available to the node. +This would result in a higher resource burden and could serve as a DoS vector. ### Why are aggregate attestations broadcast to the global topic as `AggregateAndProof`s rather than just as `Attestation`s? -The dominant strategy for an individual validator is to always broadcast an aggregate containing their own attestation to the global channel to ensure that proposers see their attestation for inclusion. Using a private selection criteria and providing this proof of selection alongside the gossiped aggregate ensures that this dominant strategy will not flood the global channel. +The dominant strategy for an individual validator is to always broadcast an aggregate containing their own attestation +to the global channel to ensure that proposers see their attestation for inclusion. +Using a private selection criteria and providing this proof of selection alongside +the gossiped aggregate ensures that this dominant strategy will not flood the global channel. -Also, an attacker can create any number of honest-looking aggregates and broadcast them to the global pubsub channel. Thus without some sort of proof of selection as an aggregator, the global channel can trivially be spammed. +Also, an attacker can create any number of honest-looking aggregates and broadcast them to the global pubsub channel. +Thus without some sort of proof of selection as an aggregator, the global channel can trivially be spammed. ### Why are we sending entire objects in the pubsub and not just hashes? -Entire objects should be sent to get the greatest propagation speeds. If only hashes are sent, then block and attestation propagation is dependent on recursive requests from each peer. In a hash-only scenario, peers could receive hashes without knowing who to download the actual contents from. Sending entire objects ensures that they get propagated through the entire network. +Entire objects should be sent to get the greatest propagation speeds. +If only hashes are sent, then block and attestation propagation is dependent on recursive requests from each peer. +In a hash-only scenario, peers could receive hashes without knowing who to download the actual contents from. +Sending entire objects ensures that they get propagated through the entire network. ### Should clients gossip blocks if they *cannot* validate the proposer signature due to not yet being synced, not knowing the head block, etc? -The prohibition of unverified-block-gossiping extends to nodes that cannot verify a signature due to not being fully synced to ensure that such (amplified) DOS attacks are not possible. +The prohibition of unverified-block-gossiping extends to nodes that cannot verify a signature +due to not being fully synced to ensure that such (amplified) DOS attacks are not possible. ### How are we going to discover peers in a gossipsub topic? In Phase 0, peers for attestation subnets will be found using the `attnets` entry in the ENR. -Although this method will be sufficient for early phases of Eth2, we aim to use the more appropriate discv5 topics for this and other similar tasks in the future. ENRs should ultimately not be used for this purpose. They are best suited to store identity, location, and capability information, rather than more volatile advertisements. +Although this method will be sufficient for early phases of Eth2, we aim to use the more appropriate discv5 topics for this and other similar tasks in the future. +ENRs should ultimately not be used for this purpose. +They are best suited to store identity, location, and capability information, rather than more volatile advertisements. ### How should fork version be used in practice? -Fork versions are to be manually updated (likely via incrementing) at each hard fork. This is to provide native domain separation for signatures as well as to aid in usefulness for identitying peers (via ENRs) and versioning network protocols (e.g. using fork version to naturally version gossipsub topics). +Fork versions are to be manually updated (likely via incrementing) at each hard fork. +This is to provide native domain separation for signatures as well as to aid in usefulness for identitying peers (via ENRs) +and versioning network protocols (e.g. using fork version to naturally version gossipsub topics). -`BeaconState.genesis_validators_root` is mixed into signature and ENR fork domains (`ForkDigest`) to aid in the ease of domain separation between chains. This allows fork versions to safely be reused across chains except for the case of contentious forks using the same genesis. In these cases, extra care should be taken to isolate fork versions (e.g. flip a high order bit in all future versions of one of the chains). +`BeaconState.genesis_validators_root` is mixed into signature and ENR fork domains (`ForkDigest`) to aid in the ease of domain separation between chains. +This allows fork versions to safely be reused across chains except for the case of contentious forks using the same genesis. +In these cases, extra care should be taken to isolate fork versions (e.g. flip a high order bit in all future versions of one of the chains). -A node locally stores all previous and future planned fork versions along with the each fork epoch. This allows for handling sync and processing messages starting from past forks/epochs. +A node locally stores all previous and future planned fork versions along with the each fork epoch. +This allows for handling sync and processing messages starting from past forks/epochs. ## Req/Resp @@ -978,43 +1229,71 @@ A node locally stores all previous and future planned fork versions along with t Requests are segregated by protocol ID to: -1. Leverage protocol routing in libp2p, such that the libp2p stack will route the incoming stream to the appropriate handler. This allows the handler function for each request type to be self-contained. For an analogy, think about how you attach HTTP handlers to a REST API server. -2. Version requests independently. In a coarser-grained umbrella protocol, the entire protocol would have to be versioned even if just one field in a single message changed. -3. Enable clients to select the individual requests/versions they support. It would no longer be a strict requirement to support all requests, and clients, in principle, could support a subset of requests and variety of versions. +1. Leverage protocol routing in libp2p, such that the libp2p stack will route the incoming stream to the appropriate handler. + This allows the handler function for each request type to be self-contained. + For an analogy, think about how you attach HTTP handlers to a REST API server. +2. Version requests independently. + In a coarser-grained umbrella protocol, the entire protocol would have to be versioned even if just one field in a single message changed. +3. Enable clients to select the individual requests/versions they support. + It would no longer be a strict requirement to support all requests, + and clients, in principle, could support a subset of requests and variety of versions. 4. Enable flexibility and agility for clients adopting spec changes that impact the request, by signalling to peers exactly which subset of new/old requests they support. -5. Enable clients to explicitly choose backwards compatibility at the request granularity. Without this, clients would be forced to support entire versions of the coarser request protocol. -6. Parallelise RFCs (or Eth2 EIPs). By decoupling requests from one another, each RFC that affects the request protocol can be deployed/tested/debated independently without relying on a synchronization point to version the general top-level protocol. - 1. This has the benefit that clients can explicitly choose which RFCs to deploy without buying into all other RFCs that may be included in that top-level version. - 2. Affording this level of granularity with a top-level protocol would imply creating as many variants (e.g. /protocol/43-{a,b,c,d,...}) as the cartesian product of RFCs inflight, O(n^2). -7. Allow us to simplify the payload of requests. Request-id’s and method-ids no longer need to be sent. The encoding/request type and version can all be handled by the framework. - -**Caveat**: The protocol negotiation component in the current version of libp2p is called multistream-select 1.0. It is somewhat naïve and introduces overhead on every request when negotiating streams, although implementation-specific optimizations are possible to save this cost. Multiselect 2.0 will eventually remove this overhead by memoizing previously selected protocols, and modeling shared protocol tables. Fortunately, this req/resp protocol is not the expected network bottleneck in the protocol so the additional overhead is not expected to significantly hinder this domain. +5. Enable clients to explicitly choose backwards compatibility at the request granularity. + Without this, clients would be forced to support entire versions of the coarser request protocol. +6. Parallelise RFCs (or Eth2 EIPs). + By decoupling requests from one another, each RFC that affects the request protocol can be deployed/tested/debated independently + without relying on a synchronization point to version the general top-level protocol. + 1. This has the benefit that clients can explicitly choose which RFCs to deploy + without buying into all other RFCs that may be included in that top-level version. + 2. Affording this level of granularity with a top-level protocol would imply creating as many variants + (e.g. /protocol/43-{a,b,c,d,...}) as the cartesian product of RFCs inflight, O(n^2). +7. Allow us to simplify the payload of requests. + Request-id’s and method-ids no longer need to be sent. + The encoding/request type and version can all be handled by the framework. + +**Caveat**: The protocol negotiation component in the current version of libp2p is called multistream-select 1.0. +It is somewhat naïve and introduces overhead on every request when negotiating streams, +although implementation-specific optimizations are possible to save this cost. +Multiselect 2.0 will eventually remove this overhead by memoizing previously selected protocols, and modeling shared protocol tables. +Fortunately, this req/resp protocol is not the expected network bottleneck in the protocol +so the additional overhead is not expected to significantly hinder this domain. ### Why are messages length-prefixed with a protobuf varint in the SSZ-encoding? -We are using single-use streams where each stream is closed at the end of the message. Thus, libp2p transparently handles message delimiting in the underlying stream. libp2p streams are full-duplex, and each party is responsible for closing their write side (like in TCP). We can therefore use stream closure to mark the end of the request and response independently. +We are using single-use streams where each stream is closed at the end of the message. +Thus, libp2p transparently handles message delimiting in the underlying stream. +libp2p streams are full-duplex, and each party is responsible for closing their write side (like in TCP). +We can therefore use stream closure to mark the end of the request and response independently. Nevertheless, in the case of `ssz` and `ssz_snappy`, messages are still length-prefixed with the length of the underlying data: * A basic reader can prepare a correctly sized buffer before reading the message * A more advanced reader can stream-decode SSZ given the length of the SSZ data. * Alignment with protocols like gRPC over HTTP/2 that prefix with length * Sanity checking of message length, and enabling much stricter message length limiting based on SSZ type information, - to provide even more DOS protection than the global message length already does. E.g. a small `Status` message does not nearly require `MAX_CHUNK_SIZE` bytes. + to provide even more DOS protection than the global message length already does. + E.g. a small `Status` message does not nearly require `MAX_CHUNK_SIZE` bytes. -[Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) is an efficient technique to encode variable-length (unsigned here) ints. Instead of reserving a fixed-size field of as many bytes as necessary to convey the maximum possible value, this field is elastic in exchange for 1-bit overhead per byte. +[Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) is an efficient technique to encode variable-length (unsigned here) ints. +Instead of reserving a fixed-size field of as many bytes as necessary to convey the maximum possible value, this field is elastic in exchange for 1-bit overhead per byte. ### Why do we version protocol strings with ordinals instead of semver? -Using semver for network protocols is confusing. It is never clear what a change in a field, even if backwards compatible on deserialization, actually implies. Network protocol agreement should be explicit. Imagine two peers: +Using semver for network protocols is confusing. +It is never clear what a change in a field, even if backwards compatible on deserialization, actually implies. +Network protocol agreement should be explicit. Imagine two peers: -- Peer A supporting v1.1.1 of protocol X. -- Peer B supporting v1.1.2 of protocol X. +- Peer A supporting v1.1.1 of protocol X. +- Peer B supporting v1.1.2 of protocol X. -These two peers should never speak to each other because the results can be unpredictable. This is an oversimplification: imagine the same problem with a set of 10 possible versions. We now have 10^2 (100) possible outcomes that peers need to model for. The resulting complexity is unwieldy. +These two peers should never speak to each other because the results can be unpredictable. +This is an oversimplification: imagine the same problem with a set of 10 possible versions. +We now have 10^2 (100) possible outcomes that peers need to model for. The resulting complexity is unwieldy. -For this reason, we rely on negotiation of explicit, verbatim protocols. In the above case, peer B would provide backwards compatibility by supporting and advertising both v1.1.1 and v1.1.2 of the protocol. +For this reason, we rely on negotiation of explicit, verbatim protocols. +In the above case, peer B would provide backwards compatibility by supporting and advertising both v1.1.1 and v1.1.2 of the protocol. -Therefore, semver would be relegated to convey expectations at the human level, and it wouldn't do a good job there either, because it's unclear if "backwards compatibility" and "breaking change" apply only to wire schema level, to behavior, etc. +Therefore, semver would be relegated to convey expectations at the human level, and it wouldn't do a good job there either, +because it's unclear if "backwards compatibility" and "breaking change" apply only to wire schema level, to behavior, etc. For this reason, we remove and replace semver with ordinals that require explicit agreement and do not mandate a specific policy for changes. @@ -1036,91 +1315,146 @@ Semantically, it is not an error that a block is missing during a slot making op Option 1 allows allows the responder to signal "no block", but this information may be wrong - for example in the case of a malicious node. -Under option 0, there is no way for a client to distinguish between a slot without a block and an incomplete response, but given that it already must contain logic to handle the uncertainty of a malicious peer, option 0 was chosen. Clients should mark any slots missing blocks as unknown until they can be verified as not containing a block by successive blocks. +Under option 0, there is no way for a client to distinguish between a slot without a block and an incomplete response, +but given that it already must contain logic to handle the uncertainty of a malicious peer, option 0 was chosen. +Clients should mark any slots missing blocks as unknown until they can be verified as not containing a block by successive blocks. -Assuming option 0 with no special `null` encoding, consider a request for slots `2, 3, 4` - if there was no block produced at slot 4, the response would be `2, 3, EOF`. Now consider the same situation, but where only `4` is requested - closing the stream with only `EOF` (without any `response_chunk`) is consistent. +Assuming option 0 with no special `null` encoding, consider a request for slots `2, 3, 4` +-- if there was no block produced at slot 4, the response would be `2, 3, EOF`. +Now consider the same situation, but where only `4` is requested +-- closing the stream with only `EOF` (without any `response_chunk`) is consistent. -Failing to provide blocks that nodes "should" have is reason to trust a peer less - for example, if a particular peer gossips a block, it should have access to its parent. If a request for the parent fails, it's indicative of poor peer quality since peers should validate blocks before gossiping them. +Failing to provide blocks that nodes "should" have is reason to trust a peer less +-- for example, if a particular peer gossips a block, it should have access to its parent. +If a request for the parent fails, it's indicative of poor peer quality since peers should validate blocks before gossiping them. ### Why does `BeaconBlocksByRange` let the server choose which branch to send blocks from? -When connecting, the `Status` message gives an idea about the sync status of a particular peer, but this changes over time. By the time a subsequent `BeaconBlockByRange` request is processed, the information may be stale, and the responding side might have moved on to a new finalization point and pruned blocks around the previous head and finalized blocks. +When connecting, the `Status` message gives an idea about the sync status of a particular peer, but this changes over time. +By the time a subsequent `BeaconBlockByRange` request is processed, the information may be stale, +and the responding side might have moved on to a new finalization point and pruned blocks around the previous head and finalized blocks. -To avoid this race condition, we allow the responding side to choose which branch to send to the requesting client. The requesting client then goes on to validate the blocks and incorporate them in their own database - because they follow the same rules, they should at this point arrive at the same canonical chain. +To avoid this race condition, we allow the responding side to choose which branch to send to the requesting client. +The requesting client then goes on to validate the blocks and incorporate them in their own database +-- because they follow the same rules, they should at this point arrive at the same canonical chain. ### What's the effect of empty slots on the sync algorithm? -When syncing one can only tell that a slot has been skipped on a particular branch by examining subsequent blocks and analyzing the graph formed by the parent root. Because the server side may choose to omit blocks in the response for any reason, clients must validate the graph and be prepared to fill in gaps. +When syncing one can only tell that a slot has been skipped on a particular branch +by examining subsequent blocks and analyzing the graph formed by the parent root. +Because the server side may choose to omit blocks in the response for any reason, clients must validate the graph and be prepared to fill in gaps. -For example, if a peer responds with blocks [2, 3] when asked for [2, 3, 4], clients may not assume that block 4 doesn't exist - it merely means that the responding peer did not send it (they may not have it yet or may maliciously be trying to hide it) and successive blocks will be needed to determine if there exists a block at slot 4 in this particular branch. +For example, if a peer responds with blocks [2, 3] when asked for [2, 3, 4], clients may not assume that block 4 doesn't exist +-- it merely means that the responding peer did not send it (they may not have it yet or may maliciously be trying to hide it) +and successive blocks will be needed to determine if there exists a block at slot 4 in this particular branch. ## Discovery ### Why are we using discv5 and not libp2p Kademlia DHT? -discv5 is a standalone protocol, running on UDP on a dedicated port, meant for peer and service discovery only. discv5 supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are, or will be, requirements in this context. +discv5 is a standalone protocol, running on UDP on a dedicated port, meant for peer and service discovery only. +discv5 supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are, or will be, requirements in this context. -On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementation with content routing and storage capabilities, both of which are irrelevant in this context. +On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementations +with content routing and storage capabilities, both of which are irrelevant in this context. -We assume that Eth 1.0 nodes will evolve to support discv5. By sharing the discovery network between Eth 1.0 and 2.0, we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, to which smaller networks are more vulnerable. It should also help light clients of both networks find nodes with specific capabilities. +We assume that Eth 1.0 nodes will evolve to support discv5. +By sharing the discovery network between Eth 1.0 and 2.0, +we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, +to which smaller networks are more vulnerable. +It should also help light clients of both networks find nodes with specific capabilities. discv5 is in the process of being audited. ### What is the difference between an ENR and a multiaddr, and why are we using ENRs? -Ethereum Node Records are self-certified node records. Nodes craft and disseminate ENRs for themselves, proving authorship via a cryptographic signature. ENRs are sequentially indexed, enabling conflicts to be resolved. +Ethereum Node Records are self-certified node records. +Nodes craft and disseminate ENRs for themselves, proving authorship via a cryptographic signature. +ENRs are sequentially indexed, enabling conflicts to be resolved. -ENRs are key-value records with string-indexed ASCII keys. They can store arbitrary information, but EIP-778 specifies a pre-defined dictionary, including IPv4 and IPv6 addresses, secp256k1 public keys, etc. +ENRs are key-value records with string-indexed ASCII keys. +They can store arbitrary information, but EIP-778 specifies a pre-defined dictionary, including IPv4 and IPv6 addresses, secp256k1 public keys, etc. -Comparing ENRs and multiaddrs is like comparing apples and oranges. ENRs are self-certified containers of identity, addresses, and metadata about a node. Multiaddrs are address strings with the peculiarity that they’re self-describing, composable and future-proof. An ENR can contain multiaddrs, and multiaddrs can be derived securely from the fields of an authenticated ENR. +Comparing ENRs and multiaddrs is like comparing apples and oranges. +ENRs are self-certified containers of identity, addresses, and metadata about a node. +Multiaddrs are address strings with the peculiarity that they’re self-describing, composable and future-proof. +An ENR can contain multiaddrs, and multiaddrs can be derived securely from the fields of an authenticated ENR. discv5 uses ENRs and we will presumably need to: 1. Add `multiaddr` to the dictionary, so that nodes can advertise their multiaddr under a reserved namespace in ENRs. – and/or – -2. Define a bi-directional conversion function between multiaddrs and the corresponding denormalized fields in an ENR (ip, ip6, tcp, tcp6, etc.), for compatibility with nodes that do not support multiaddr natively (e.g. Eth 1.0 nodes). +2. Define a bi-directional conversion function between multiaddrs and the corresponding denormalized fields in an ENR + (ip, ip6, tcp, tcp6, etc.), for compatibility with nodes that do not support multiaddr natively (e.g. Eth 1.0 nodes). ### Why do we not form ENRs and find peers until genesis block/state is known? -Although client software might very well be running locally prior to the solidification of the eth2 genesis state and block, clients cannot form valid ENRs prior to this point. ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cleaner separation between chains so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. Once genesis data is known, we can then form ENRs and safely find peers. +Although client software might very well be running locally prior to the solidification of the eth2 genesis state and block, +clients cannot form valid ENRs prior to this point. +ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cleaner separation between chains +so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. +Once genesis data is known, we can then form ENRs and safely find peers. -When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (48hours in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis. +When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (48hours in mainnet configuration) before `genesis_time`, +providing ample time to find peers and form initial connections and gossip subnets prior to genesis. ## Compression/Encoding ### Why are we using SSZ for encoding? -SSZ is used at the consensus layer, and all implementations should have support for SSZ-encoding/decoding, requiring no further dependencies to be added to client implementations. This is a natural choice for serializing objects to be sent across the wire. The actual data in most protocols will be further compressed for efficiency. +SSZ is used at the consensus layer, and all implementations should have support for SSZ-encoding/decoding, +requiring no further dependencies to be added to client implementations. +This is a natural choice for serializing objects to be sent across the wire. +The actual data in most protocols will be further compressed for efficiency. -SSZ has well-defined schemas for consensus objects (typically sent across the wire) reducing any serialization schema data that needs to be sent. It also has defined all required types that are required for this network specification. +SSZ has well-defined schemas for consensus objects (typically sent across the wire) reducing any serialization schema data that needs to be sent. +It also has defined all required types that are required for this network specification. ### Why are we compressing, and at which layers? -We compress on the wire to achieve smaller payloads per-message, which, in aggregate, result in higher efficiency, better utilization of available bandwidth, and overall reduction in network-wide traffic overhead. +We compress on the wire to achieve smaller payloads per-message, which, in aggregate, +result in higher efficiency, better utilization of available bandwidth, and overall reduction in network-wide traffic overhead. -At this time, libp2p does not have an out-of-the-box compression feature that can be dynamically negotiated and layered atop connections and streams, but it is [being considered](https://github.com/libp2p/libp2p/issues/81). +At this time, libp2p does not have an out-of-the-box compression feature that can be dynamically negotiated +and layered atop connections and streams, but it is [being considered](https://github.com/libp2p/libp2p/issues/81). -This is a non-trivial feature because the behavior of network IO loops, kernel buffers, chunking, and packet fragmentation, amongst others, need to be taken into account. libp2p streams are unbounded streams, whereas compression algorithms work best on bounded byte streams of which we have some prior knowledge. +This is a non-trivial feature because the behavior +of network IO loops, kernel buffers, chunking, and packet fragmentation, amongst others, need to be taken into account. +libp2p streams are unbounded streams, whereas compression algorithms work best on bounded byte streams of which we have some prior knowledge. -Compression tends not to be a one-size-fits-all problem. A lot of variables need careful evaluation, and generic approaches/choices lead to poor size shavings, which may even be counterproductive when factoring in the CPU and memory tradeoff. +Compression tends not to be a one-size-fits-all problem. +A lot of variables need careful evaluation, and generic approaches/choices lead to poor size shavings, +which may even be counterproductive when factoring in the CPU and memory tradeoff. -For all these reasons, generically negotiating compression algorithms may be treated as a research problem at the libp2p community, one we’re happy to tackle in the medium-term. +For all these reasons, generically negotiating compression algorithms may be treated as a research problem at the libp2p community, +one we’re happy to tackle in the medium-term. -At this stage, the wisest choice is to consider libp2p a messenger of bytes, and to make application layer participate in compressing those bytes. This looks different depending on the interaction layer: +At this stage, the wisest choice is to consider libp2p a messenger of bytes, +and to make application layer participate in compressing those bytes. +This looks different depending on the interaction layer: -- Gossip domain: since gossipsub has a framing protocol and exposes an API, we compress the payload (when dictated by the encoding token in the topic name) prior to publishing the message via the API. No length-prefixing is necessary because protobuf takes care of bounding the field in the serialized form. -- Req/Resp domain: since we define custom protocols that operate on byte streams, implementers are encouraged to encapsulate the encoding and compression logic behind MessageReader and MessageWriter components/strategies that can be layered on top of the raw byte streams. +- Gossip domain: since gossipsub has a framing protocol and exposes an API, we compress the payload + (when dictated by the encoding token in the topic name) prior to publishing the message via the API. + No length-prefixing is necessary because protobuf takes care of bounding the field in the serialized form. +- Req/Resp domain: since we define custom protocols that operate on byte streams, + implementers are encouraged to encapsulate the encoding and compression logic behind + MessageReader and MessageWriter components/strategies that can be layered on top of the raw byte streams. ### Why are using Snappy for compression? -Snappy is used in Ethereum 1.0. It is well maintained by Google, has good benchmarks, and can calculate the size of the uncompressed object without inflating it in memory. This prevents DOS vectors where large uncompressed data is sent. +Snappy is used in Ethereum 1.0. It is well maintained by Google, has good benchmarks, +and can calculate the size of the uncompressed object without inflating it in memory. +This prevents DOS vectors where large uncompressed data is sent. ### Can I get access to unencrypted bytes on the wire for debugging purposes? -Yes, you can add loggers in your libp2p protocol handlers to log incoming and outgoing messages. It is recommended to use programming design patterns to encapsulate the logging logic cleanly. +Yes, you can add loggers in your libp2p protocol handlers to log incoming and outgoing messages. +It is recommended to use programming design patterns to encapsulate the logging logic cleanly. -If your libp2p library relies on frameworks/runtimes such as Netty (jvm) or Node.js (javascript), you can use logging facilities in those frameworks/runtimes to enable message tracing. +If your libp2p library relies on frameworks/runtimes such as Netty (jvm) or Node.js (javascript), +you can use logging facilities in those frameworks/runtimes to enable message tracing. -For specific ad-hoc testing scenarios, you can use the [plaintext/2.0.0 secure channel](https://github.com/libp2p/specs/blob/master/plaintext/README.md) (which is essentially no-op encryption or message authentication), in combination with tcpdump or Wireshark to inspect the wire. +For specific ad-hoc testing scenarios, you can use the [plaintext/2.0.0 secure channel](https://github.com/libp2p/specs/blob/master/plaintext/README.md) +(which is essentially no-op encryption or message authentication), in combination with tcpdump or Wireshark to inspect the wire. ### What are SSZ type size bounds? @@ -1128,8 +1462,10 @@ The SSZ encoding outputs of each type have size bounds: each dynamic type, such Note that for some more complex dynamic-length objects, element offsets (4 bytes each) may need to be included. Other types are static, they have a fixed size: no dynamic-length content is involved, and the minimum and maximum bounds are the same. -For reference, the type bounds can be computed ahead of time, [as per this example](https://gist.github.com/protolambda/db75c7faa1e94f2464787a480e5d613e). It is advisable to derive these lengths from the SSZ type definitions in use, to ensure that version changes do not cause out-of-sync type bounds. +For reference, the type bounds can be computed ahead of time, [as per this example](https://gist.github.com/protolambda/db75c7faa1e94f2464787a480e5d613e). +It is advisable to derive these lengths from the SSZ type definitions in use, to ensure that version changes do not cause out-of-sync type bounds. # libp2p implementations matrix -This section will soon contain a matrix showing the maturity/state of the libp2p features required by this spec across the languages in which Eth2 clients are being developed. +This section will soon contain a matrix showing the maturity/state of the libp2p features required +by this spec across the languages in which Eth2 clients are being developed. From 953d10616378a62c94340c0d5f64ac49f287ab98 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Jul 2020 12:34:39 -0600 Subject: [PATCH 207/236] add queueing possibility to p2p messages in gossip --- specs/phase0/p2p-interface.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index f0c3c80b62..e4e012f7f4 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -284,10 +284,13 @@ Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network. - _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot). + i.e. validate that `signed_beacon_block.message.slot <= current_slot` + (a client MAY queue future blocks for processing at the appropriate slot). - _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). +- _[IGNORE]_ The block's parent (`block.parent_root`) has been seen locally + (a client MAY queue blocks for processing once the parent block is retrieved). - _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. - _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. - _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot @@ -310,6 +313,8 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). - _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. +- _[IGNORE]]_ The block being voted for (`aggregate.data.beacon_block_root`) has been seen locally + (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. - _[REJECT]_ The attestation has participants -- that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. @@ -372,6 +377,8 @@ The following validations MUST pass before forwarding the `attestation` on the s (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). +- _[IGNORE]]_ The block being voted for (`attestation.data.beacon_block_root`) has been seen locally + (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet From 4c1fa7fa6fb4a6d3ed4234080d0a330ebe2a344e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Jul 2020 12:41:41 -0600 Subject: [PATCH 208/236] add note about max queue sizes in gossip --- specs/phase0/p2p-interface.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index e4e012f7f4..d1d85fc76f 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -263,6 +263,8 @@ Clients MUST reject (fail validation) messages containing an incorrect type, or When processing incoming gossip, clients MAY descore or disconnect peers who fail to observe these constraints. +For any optional queueing, clients SHOULD maintain maximum queue sizes to avoid DoS vectors. + Gossipsub v1.1 introduces [Extended Validators](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators) for the application to aid in the gossipsub peer-scoring scheme. We utilize `ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are application specific validations. From 334947f5231ed72ffa86081ea9caea2b02df9770 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 7 Jul 2020 16:15:43 -0600 Subject: [PATCH 209/236] rearrange queuing conditions --- specs/phase0/p2p-interface.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index d1d85fc76f..0a976589ce 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -291,8 +291,6 @@ The following validations MUST pass before forwarding the `signed_beacon_block` - _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). -- _[IGNORE]_ The block's parent (`block.parent_root`) has been seen locally - (a client MAY queue blocks for processing once the parent block is retrieved). - _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. - _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. - _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot @@ -300,6 +298,10 @@ The following validations MUST pass before forwarding the `signed_beacon_block` If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. +- _[IGNORE]_ The block's parent (defined by `block.parent_root`) has been seen + (via both gossip and non-gossip sources) + (a client MAY queue blocks for processing once the parent block is retrieved). +- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes validation. ##### `beacon_aggregate_and_proof` @@ -315,9 +317,6 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). - _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. -- _[IGNORE]]_ The block being voted for (`aggregate.data.beacon_block_root`) has been seen locally - (a client MAY queue aggregates for processing once block is retrieved). -- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. - _[REJECT]_ The attestation has participants -- that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. - _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- @@ -328,6 +327,10 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. - _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, is valid. - _[REJECT]_ The signature of `aggregate` is valid. +- _[IGNORE]_ The block being voted for (`aggregate.data.beacon_block_root`) has been seen + (via both gossip and non-gossip sources) + (a client MAY queue aggregates for processing once block is retrieved). +- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. ##### `voluntary_exit` @@ -379,14 +382,15 @@ The following validations MUST pass before forwarding the `attestation` on the s (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` (a client MAY queue future attestations for processing at the appropriate slot). -- _[IGNORE]]_ The block being voted for (`attestation.data.beacon_block_root`) has been seen locally - (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet that has an identical `attestation.data.target.epoch` and participating validator index. -- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. - _[REJECT]_ The signature of `attestation` is valid. +- _[IGNORE]_ The block being voted for (`attestation.data.beacon_block_root`) has been seen + (via both gossip and non-gossip sources) + (a client MAY queue aggregates for processing once block is retrieved). +- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. #### Attestations and Aggregation From 07274736b978cf61b05739d1d86abbf29cc1b69d Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 8 Jul 2020 01:42:09 +0200 Subject: [PATCH 210/236] Match gossip v1.1 D_low, extend gossip_history param, add FAQ section --- specs/phase0/p2p-interface.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index f0c3c80b62..b26bfeeb21 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -79,6 +79,7 @@ It consists of four main sections: - [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair) - [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes) - [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id) + - [Why are these specific gossip parameters chosen?](#why-are-these-specific-gossip-parameters-chosen) - [Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?](#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets) - [Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets?](#why-are-there-attestation_subnet_count-attestation-subnets) - [Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots?](#why-are-attestations-limited-to-be-broadcast-on-gossip-channels-within-slots_per_epoch-slots) @@ -213,12 +214,12 @@ including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsu The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub#meshsub-an-overlay-mesh-router) will be used: - `D` (topic stable mesh target count): 6 -- `D_low` (topic stable mesh low watermark): 4 +- `D_low` (topic stable mesh low watermark): 5 - `D_high` (topic stable mesh high watermark): 12 - `D_lazy` (gossip target): 6 - `fanout_ttl` (ttl for fanout maps for topics we are not subscribed to but have published to, seconds): 60 - `gossip_advertise` (number of windows to gossip about): 3 -- `gossip_history` (number of heartbeat intervals to retain message IDs): 5 +- `gossip_history` (number of heartbeat intervals to retain message IDs): 385 - `heartbeat_interval` (frequency of heartbeat, seconds): 1 ### Topics and messages @@ -1146,6 +1147,17 @@ Some examples of where messages could be duplicated: Partial aggregates could be duplicated * Clients re-publishing seen messages +### Why are these specific gossip parameters chosen? + +- `D`, `D_low`, `D_high`, `D_lazy`: recommended defaults. +- `fanout_ttl`: 60 (TODO: increase to cover an epoch?) +- `gossip_advertise`: 3 (TODO: to increase to 6?) +- `gossip_history`: `SLOTS_PER_EPOCH * SECONDS_PER_SLOT / heartbeat_interval = approx. 385`. Attestation validity is bounded by an epoch, so this is the safe max bound. +- `heartbeat_interval`: 1 second, recommended default. + For Eth2 specifically, 0.6-0.7s was recommended in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). + And this parameter will be experimented with in testnets. + + ### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets? For some gossip channels (e.g. those for Attestations and BeaconBlocks), From bc35dc410bacdff9c67d971f8150e8e3e9b18fa9 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 8 Jul 2020 15:40:49 +0200 Subject: [PATCH 211/236] fix block data root type, thanks @ericsson49 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ddf1785947..214559d222 100644 --- a/setup.py +++ b/setup.py @@ -228,7 +228,7 @@ def wrapper(*args, **kw): # type: ignore def get_block_data_merkle_root(data: ByteList) -> Root: # To get the Merkle root of the block data, we need the Merkle root without the length mix-in # The below implements this in the Remerkleable framework - return data.get_backing().get_left().merkle_root() + return Root(data.get_backing().get_left().merkle_root()) _get_start_shard = get_start_shard From 3e9556202a5535be87e2189c90330f75c5e3c014 Mon Sep 17 00:00:00 2001 From: ericsson Date: Wed, 8 Jul 2020 18:19:41 +0300 Subject: [PATCH 212/236] change `get_custody_period_for_validator()` return type to `uint64` --- specs/phase1/custody-game.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index b89741e03d..3688442a0c 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -291,7 +291,7 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat ### `get_custody_period_for_validator` ```python -def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> int: +def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> uint64: ''' Return the reveal period for a given validator. ''' From b785c970510eab7dc50a739f35412b84fd38cd0a Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Wed, 8 Jul 2020 21:58:20 +0600 Subject: [PATCH 213/236] Fix epoch_boundary_block_root computation --- specs/phase0/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index a3bbcac32a..bdd6f170d2 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -399,7 +399,7 @@ Set `attestation_data.beacon_block_root = hash_tree_root(head_block)`. *Note*: `epoch_boundary_block_root` can be looked up in the state using: - Let `start_slot = compute_start_slot_at_epoch(get_current_epoch(head_state))`. -- Let `epoch_boundary_block_root = hash_tree_root(head_block) if start_slot == head_state.slot else get_block_root(state, start_slot)`. +- Let `epoch_boundary_block_root = hash_tree_root(head_block) if start_slot == head_state.slot else get_block_root(state, get_current_epoch(head_state))`. #### Construct attestation From 302607eac56c94a7be1ca7a01f5419d93cdf4b79 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 21 Jul 2020 21:10:27 +0800 Subject: [PATCH 214/236] Rename `aggregate_na_pubkeys` to `aggregate_na_signatures` and add comments --- tests/generators/bls/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 9e10b4044b..bfe0c8342a 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -149,7 +149,9 @@ def case03_aggregate(): else: raise Exception("Should have been INVALID") - yield f'aggregate_na_pubkeys', { + # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. + # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8 + yield f'aggregate_na_signatures', { 'input': [], 'output': None, } From ec7be11c068aaa20f21eec80295516cea0a122ea Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 21 Jul 2020 16:45:25 -0600 Subject: [PATCH 215/236] mod gossip params and rename to reflect spec names --- specs/phase0/p2p-interface.md | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index b26bfeeb21..2d9962d6ca 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -202,7 +202,7 @@ and will in most cases be out of sync with the ENR sequence number. ## The gossip domain: gossipsub -Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) libp2p Protocol +Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md) libp2p Protocol including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) extension. **Protocol ID:** `/meshsub/1.1.0` @@ -211,16 +211,17 @@ including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsu *Note*: Parameters listed here are subject to a large-scale network feasibility study. -The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub#meshsub-an-overlay-mesh-router) will be used: +The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used: - `D` (topic stable mesh target count): 6 - `D_low` (topic stable mesh low watermark): 5 - `D_high` (topic stable mesh high watermark): 12 - `D_lazy` (gossip target): 6 +- `heartbeat_interval` (frequency of heartbeat, seconds): 0.7 - `fanout_ttl` (ttl for fanout maps for topics we are not subscribed to but have published to, seconds): 60 -- `gossip_advertise` (number of windows to gossip about): 3 -- `gossip_history` (number of heartbeat intervals to retain message IDs): 385 -- `heartbeat_interval` (frequency of heartbeat, seconds): 1 +- `mcache_len` (number of windows to retain full messages in cache for `IWANT` responses): 6 +- `mcache_gossip` (number of windows to gossip about): 3 +- `seen_ttl` (number of heartbeat intervals to retain message IDs): 550 ### Topics and messages @@ -1150,12 +1151,21 @@ Some examples of where messages could be duplicated: ### Why are these specific gossip parameters chosen? - `D`, `D_low`, `D_high`, `D_lazy`: recommended defaults. -- `fanout_ttl`: 60 (TODO: increase to cover an epoch?) -- `gossip_advertise`: 3 (TODO: to increase to 6?) -- `gossip_history`: `SLOTS_PER_EPOCH * SECONDS_PER_SLOT / heartbeat_interval = approx. 385`. Attestation validity is bounded by an epoch, so this is the safe max bound. -- `heartbeat_interval`: 1 second, recommended default. - For Eth2 specifically, 0.6-0.7s was recommended in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). - And this parameter will be experimented with in testnets. +- `heartbeat_interval`: 0.7 second, recommended for eth2 in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). +- `fanout_ttl`: 60, recommended default. + Fanout is primarily used by committees publishing attestations to subnets. + This happens once per epoch per validator and the subnet changes each epoch + so there is little to gain in having a `fanout_ttl` be increased from the recommended default. +- `mcache_len`: 6, increase by one to ensure that mcache is around for long + enough for `IWANT`s to respond to `IHAVE`s in the context of the shorter + `heartbeat_interval`. If `mcache_gossip` is increased, this param should be + increased to be at least `3` (~2 seconds) more than `mcache_gossip`. + should be increased to be at least +- `mcache_gossip`: 3, recommended default. This can be increased to 5 or 6 + (~4 seconds) if gossip times are longer than expected and the current window + does not provide enough responsiveness during adverse conditions. +- `seen_ttl`: `SLOTS_PER_EPOCH * SECONDS_PER_SLOT / heartbeat_interval = approx. 550`. + Attestation gossip validity is bounded by an epoch, so this is the safe max bound. ### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets? From edcce7bfefe13a1042aaa563dd03c485e5d8f1a4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 21 Jul 2020 17:00:11 -0600 Subject: [PATCH 216/236] format --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 2d9962d6ca..942d802437 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1151,8 +1151,8 @@ Some examples of where messages could be duplicated: ### Why are these specific gossip parameters chosen? - `D`, `D_low`, `D_high`, `D_lazy`: recommended defaults. -- `heartbeat_interval`: 0.7 second, recommended for eth2 in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). -- `fanout_ttl`: 60, recommended default. +- `heartbeat_interval`: 0.7 seconds, recommended for eth2 in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). +- `fanout_ttl`: 60 seconds, recommended default. Fanout is primarily used by committees publishing attestations to subnets. This happens once per epoch per validator and the subnet changes each epoch so there is little to gain in having a `fanout_ttl` be increased from the recommended default. From 50cd0b2b31b544c368391eae25d7b395f007ea58 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 21 Jul 2020 17:49:39 -0600 Subject: [PATCH 217/236] Update specs/phase0/p2p-interface.md Co-authored-by: Diederik Loerakker --- specs/phase0/p2p-interface.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 942d802437..2eac35c94e 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1160,7 +1160,6 @@ Some examples of where messages could be duplicated: enough for `IWANT`s to respond to `IHAVE`s in the context of the shorter `heartbeat_interval`. If `mcache_gossip` is increased, this param should be increased to be at least `3` (~2 seconds) more than `mcache_gossip`. - should be increased to be at least - `mcache_gossip`: 3, recommended default. This can be increased to 5 or 6 (~4 seconds) if gossip times are longer than expected and the current window does not provide enough responsiveness during adverse conditions. From a64b8eba6e8b76db89c149d063dbd303a62d4656 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 09:37:16 -0600 Subject: [PATCH 218/236] remove 'ssz' format from req/resp. now only ssz_snappy --- specs/phase0/p2p-interface.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index f792829eff..44cbb79828 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -41,7 +41,7 @@ It consists of four main sections: - [Requesting side](#requesting-side) - [Responding side](#responding-side) - [Encoding strategies](#encoding-strategies) - - [SSZ-encoding strategy (with or without Snappy)](#ssz-encoding-strategy-with-or-without-snappy) + - [SSZ-snappy encoding strategy](#ssz-snappy-encoding-strategy) - [Messages](#messages) - [Status](#status) - [Goodbye](#goodbye) @@ -550,20 +550,18 @@ Clients MUST treat as valid any byte sequences. ### Encoding strategies The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. -Two values are possible at this time: +Only one value is possible at this time: -- `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md). - This encoding type MUST be supported by all clients. +- `ssz_snappy`: The contents are first [SSZ-encoded](../../ssz/simple-serialize.md) + and then compressed with [Snappy](https://github.com/google/snappy) frames compression. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. - For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s. -- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) frames compression. This encoding type MUST be supported by all clients. -#### SSZ-encoding strategy (with or without Snappy) +#### SSZ-snappy encoding strategy The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded. -If the Snappy variant is selected, we feed the serialized form of the object to the Snappy compressor on encoding. +To achieve snappy encoding on top of SSZ, we feed the serialized form of the object to the Snappy compressor on encoding. The inverse happens on decoding. Snappy has two formats: "block" and "frames" (streaming). @@ -572,14 +570,14 @@ To support large requests and response chunks, snappy-framing is used. Since snappy frame contents [have a maximum size of `65536` bytes](https://github.com/google/snappy/blob/master/framing_format.txt#L104) and frame headers are just `identifier (1) + checksum (4)` bytes, the expected buffering of a single frame is acceptable. -**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST encode the length of the raw SSZ bytes, +**Encoding-dependent header:** Req/Resp protocols using the `ssz_snappy` encoding strategy MUST encode the length of the raw SSZ bytes, encoded as an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). *Writing*: By first computing and writing the SSZ byte length, the SSZ encoder can then directly write the chunk contents to the stream. -If Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame. +When Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame. *Reading*: After reading the expected SSZ byte length, the SSZ decoder can directly read the contents from the stream. -If snappy is applied, it can be passed through a buffered Snappy reader to decompress frame by frame. +When snappy is applied, it can be passed through a buffered Snappy reader to decompress frame by frame. Before reading the payload, the header MUST be validated: - The unsigned protobuf varint used for the length-prefix MUST not be longer than 10 bytes, which is sufficient for any `uint64`. @@ -588,7 +586,6 @@ Before reading the payload, the header MUST be validated: After reading a valid header, the payload MAY be read, while maintaining the size constraints from the header. A reader SHOULD NOT read more than `max_encoded_len(n)` bytes after reading the SSZ length-prefix `n` from the header. -- For `ssz` this is: `n` - For `ssz_snappy` this is: `32 + n + n // 6`. This is considered the [worst-case compression result](https://github.com/google/snappy/blob/537f4ad6240e586970fe554614542e9717df7902/snappy.cc#L98) by Snappy. @@ -1299,7 +1296,7 @@ Thus, libp2p transparently handles message delimiting in the underlying stream. libp2p streams are full-duplex, and each party is responsible for closing their write side (like in TCP). We can therefore use stream closure to mark the end of the request and response independently. -Nevertheless, in the case of `ssz` and `ssz_snappy`, messages are still length-prefixed with the length of the underlying data: +Nevertheless, in the case of `ssz_snappy`, messages are still length-prefixed with the length of the underlying data: * A basic reader can prepare a correctly sized buffer before reading the message * A more advanced reader can stream-decode SSZ given the length of the SSZ data. * Alignment with protocols like gRPC over HTTP/2 that prefix with length From 289564aec03de5cb211ed4c5b57014349071a92e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 10:03:43 -0600 Subject: [PATCH 219/236] pr feedback --- specs/phase0/p2p-interface.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 44cbb79828..aeddd8b513 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -555,6 +555,7 @@ Only one value is possible at this time: - `ssz_snappy`: The contents are first [SSZ-encoded](../../ssz/simple-serialize.md) and then compressed with [Snappy](https://github.com/google/snappy) frames compression. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. + For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s. This encoding type MUST be supported by all clients. #### SSZ-snappy encoding strategy From fb13f67cca1ea8878f6d3a83f9252354b9aaab7c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 10:10:21 -0600 Subject: [PATCH 220/236] add that current block is in the same chain as finalized ancestor --- specs/phase0/p2p-interface.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index f792829eff..1d45ebae8b 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -295,15 +295,18 @@ The following validations MUST pass before forwarding the `signed_beacon_block` (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). - _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. - _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. +- _[IGNORE]_ The block's parent (defined by `block.parent_root`) has been seen + (via both gossip and non-gossip sources) + (a client MAY queue blocks for processing once the parent block is retrieved). +- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes validation. +- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of `block` -- i.e. + `get_ancestor(store, block.parent_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) + == store.finalized_checkpoint.root` - _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the block MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. -- _[IGNORE]_ The block's parent (defined by `block.parent_root`) has been seen - (via both gossip and non-gossip sources) - (a client MAY queue blocks for processing once the parent block is retrieved). -- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes validation. ##### `beacon_aggregate_and_proof` From 0692ac017235c37f33dcc38455ef32273058501f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 10:28:02 -0600 Subject: [PATCH 221/236] bump VERSION.txt to 0.12.2 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index aac2dacab4..e96a87111c 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -0.12.1 \ No newline at end of file +0.12.2 \ No newline at end of file From b3e49ff0d3f0667ceefebb8ec393694fa77db316 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 10:39:04 -0600 Subject: [PATCH 222/236] add finalized ancestor checks to attestation gossip --- specs/phase0/p2p-interface.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 1d45ebae8b..0f70180e21 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -336,6 +336,10 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ (via both gossip and non-gossip sources) (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. +- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e. + `get_ancestor(store, hash_tree_root(block), compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) + == store.finalized_checkpoint.root` + ##### `voluntary_exit` @@ -396,6 +400,11 @@ The following validations MUST pass before forwarding the `attestation` on the s (via both gossip and non-gossip sources) (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. +- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `attestation.data.beacon_block_root` -- i.e. + `get_ancestor(store, hash_tree_root(block), compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) + == store.finalized_checkpoint.root` + + #### Attestations and Aggregation From 3cf683219874e80d3ae5043c1ae200ddf0ece056 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 10:46:31 -0600 Subject: [PATCH 223/236] Apply suggestions from code review Co-authored-by: Diederik Loerakker --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 0f70180e21..6acefb9ad5 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -337,7 +337,7 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. - _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e. - `get_ancestor(store, hash_tree_root(block), compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) + `get_ancestor(store, aggregate.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) == store.finalized_checkpoint.root` @@ -401,7 +401,7 @@ The following validations MUST pass before forwarding the `attestation` on the s (a client MAY queue aggregates for processing once block is retrieved). - _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. - _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `attestation.data.beacon_block_root` -- i.e. - `get_ancestor(store, hash_tree_root(block), compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) + `get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) == store.finalized_checkpoint.root` From 9193a23f8eeb4bf83c2b9624f4a8d3d762f23191 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 12:05:22 -0600 Subject: [PATCH 224/236] add chain id and netowrk id to config --- configs/mainnet/phase0.yaml | 3 +++ configs/minimal/phase0.yaml | 3 +++ specs/phase0/deposit-contract.md | 4 +++- specs/phase0/validator.md | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index 39cfddf770..616baaf345 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -51,6 +51,9 @@ SECONDS_PER_ETH1_BLOCK: 14 # Deposit contract # --------------------------------------------------------------- +# Ethereum PoW Mainnet +DEPOSIT_CHAIN_ID: 1 +DEPOSIT_NETWORK_ID: 1 # **TBD** DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 diff --git a/configs/minimal/phase0.yaml b/configs/minimal/phase0.yaml index 524d0d5f9c..78cd601770 100644 --- a/configs/minimal/phase0.yaml +++ b/configs/minimal/phase0.yaml @@ -51,6 +51,9 @@ SECONDS_PER_ETH1_BLOCK: 14 # Deposit contract # --------------------------------------------------------------- +# Ethereum Goerli testnet +DEPOSIT_CHAIN_ID: 5 +DEPOSIT_NETWORK_ID: 5 # **TBD** DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 9c5137a32c..68d281614b 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -31,12 +31,14 @@ This document represents the specification for the beacon chain deposit contract | Name | Value | | - | - | +| `DEPOSIT_CHAIN_ID` | `1` | +| `DEPOSIT_NETWORK_ID` | `1` | | `DEPOSIT_CONTRACT_ADDRESS` | **TBD** | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | ## 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 in Phase 2. +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 chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID` for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2. ### `deposit` function diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 6a66dc707f..e379df03c2 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -111,7 +111,7 @@ The validator constructs their `withdrawal_credentials` via the following: ### Submit deposit -In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 proof-of-work chain. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. +In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID`. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. To submit a deposit: From 4c3f866a0ff012efa642db9fc09280fd0b90ba2a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 12:25:44 -0600 Subject: [PATCH 225/236] add links for network and chain id --- specs/phase0/deposit-contract.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 68d281614b..db95661897 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -38,7 +38,9 @@ This document represents the specification for the beacon chain deposit contract ## 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 chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID` for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2. +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 the Ethereum 1.0 chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2. + +_Note_: See [here](https://chainid.network/) for a comprehensive list of public Ethereum chain chain-id's and network-id's. ### `deposit` function From bdde2e588968e26a6ffe5748570693950c547bd2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Jul 2020 03:52:41 +0800 Subject: [PATCH 226/236] Reorg test dir --- .../pyspec/eth2spec/test/{ => phase0/finality}/test_finality.py | 0 .../core/pyspec/eth2spec/test/{ => phase0}/genesis/__init__.py | 0 .../eth2spec/test/{ => phase0}/genesis/test_initialization.py | 0 .../pyspec/eth2spec/test/{ => phase0}/genesis/test_validity.py | 0 .../test/{ => phase0/unittests}/fork_choice/test_get_head.py | 0 .../{ => phase0/unittests}/fork_choice/test_on_attestation.py | 0 .../test/{ => phase0/unittests}/fork_choice/test_on_block.py | 0 .../test/{ => phase0/unittests}/fork_choice/test_on_tick.py | 0 .../{ => phase0/unittests}/validator/test_validator_unittest.py | 0 .../{ => phase1/unittests}/fork_choice/test_on_shard_head.py | 0 tests/generators/genesis/main.py | 2 +- 11 files changed, 1 insertion(+), 1 deletion(-) rename tests/core/pyspec/eth2spec/test/{ => phase0/finality}/test_finality.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0}/genesis/__init__.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0}/genesis/test_initialization.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0}/genesis/test_validity.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0/unittests}/fork_choice/test_get_head.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0/unittests}/fork_choice/test_on_attestation.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0/unittests}/fork_choice/test_on_block.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0/unittests}/fork_choice/test_on_tick.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase0/unittests}/validator/test_validator_unittest.py (100%) rename tests/core/pyspec/eth2spec/test/{ => phase1/unittests}/fork_choice/test_on_shard_head.py (100%) diff --git a/tests/core/pyspec/eth2spec/test/test_finality.py b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/test_finality.py rename to tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py diff --git a/tests/core/pyspec/eth2spec/test/genesis/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/genesis/__init__.py rename to tests/core/pyspec/eth2spec/test/phase0/genesis/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/genesis/test_initialization.py rename to tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/genesis/test_validity.py rename to tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/fork_choice/test_get_head.py rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_attestation.py rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_block.py rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_tick.py rename to tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py diff --git a/tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/validator/test_validator_unittest.py rename to tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py diff --git a/tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/fork_choice/test_on_shard_head.py rename to tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py index 8548b12c19..a978ab6849 100644 --- a/tests/generators/genesis/main.py +++ b/tests/generators/genesis/main.py @@ -1,7 +1,7 @@ from typing import Iterable from eth2spec.test.context import PHASE0 -from eth2spec.test.genesis import test_initialization, test_validity +from eth2spec.test.phase0.genesis import test_initialization, test_validity from gen_base import gen_runner, gen_typing from gen_from_tests.gen import generate_from_tests From de15960185804a82d41ef6f1df62a798cb7b8af5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Jul 2020 04:15:48 +0800 Subject: [PATCH 227/236] Add finality tests to test vectors --- .../eth2spec/test/phase0/finality/__init__.py | 0 tests/formats/finality/README.md | 43 +++++++++++++++++++ tests/generators/finality/README.md | 5 +++ tests/generators/finality/main.py | 37 ++++++++++++++++ tests/generators/finality/requirements.txt | 2 + 5 files changed, 87 insertions(+) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/finality/__init__.py create mode 100644 tests/formats/finality/README.md create mode 100644 tests/generators/finality/README.md create mode 100644 tests/generators/finality/main.py create mode 100644 tests/generators/finality/requirements.txt diff --git a/tests/core/pyspec/eth2spec/test/phase0/finality/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/finality/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/formats/finality/README.md b/tests/formats/finality/README.md new file mode 100644 index 0000000000..3e2b40be37 --- /dev/null +++ b/tests/formats/finality/README.md @@ -0,0 +1,43 @@ +# Finality tests + +The aim of the tests for the finality rules. + +- [`finality`](./finality.md): transitions triggered by one or more blocks. + +## Test case format + +### `meta.yaml` + +```yaml +description: string -- Optional. Description of test case, purely for debugging purposes. +bls_setting: int -- see general test-format spec. +blocks_count: int -- the number of blocks processed in this test. +``` + +### `pre.yaml` + +A YAML-encoded `BeaconState`, the state before running the block transitions. + +Also available as `pre.ssz`. + + +### `blocks_.yaml` + +A series of files, with `` in range `[0, blocks_count)`. Blocks need to be processed in order, + following the main transition function (i.e. process slot and epoch transitions in between blocks as normal) + +Each file is a YAML-encoded `SignedBeaconBlock`. + +Each block is also available as `blocks_.ssz` + +### `post.yaml` + +A YAML-encoded `BeaconState`, the state after applying the block transitions. + +Also available as `post.ssz`. + + +## Condition + +The resulting state should match the expected `post` state, or if the `post` state is left blank, + the handler should reject the series of blocks as invalid. diff --git a/tests/generators/finality/README.md b/tests/generators/finality/README.md new file mode 100644 index 0000000000..dec5819c68 --- /dev/null +++ b/tests/generators/finality/README.md @@ -0,0 +1,5 @@ +# Finality tests + +Finality tests cover regular state-transitions in a common block-list format to test finality rules. + +Information on the format of the tests can be found in the [finality test formats documentation](../../formats/finality/README.md). diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py new file mode 100644 index 0000000000..5ab5a98e1a --- /dev/null +++ b/tests/generators/finality/main.py @@ -0,0 +1,37 @@ +from typing import Iterable +from importlib import reload + +from gen_base import gen_runner, gen_typing +from gen_from_tests.gen import generate_from_tests + +from eth2spec.test.context import PHASE0 +from eth2spec.test.phase0.finality import test_finality +from eth2spec.config import config_util +from eth2spec.phase0 import spec as spec_phase0 +from eth2spec.phase1 import spec as spec_phase1 + + +def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: + + def prepare_fn(configs_path: str) -> str: + config_util.prepare_config(configs_path, config_name) + reload(spec_phase0) + reload(spec_phase1) + return config_name + + def cases_fn() -> Iterable[gen_typing.TestCase]: + return generate_from_tests( + runner_name='finality', + handler_name=handler_name, + src=tests_src, + fork_name=PHASE0, + ) + + return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) + + +if __name__ == "__main__": + gen_runner.run_generator("finality", [ + create_provider('finality', test_finality, 'minimal'), + create_provider('finality', test_finality, 'mainnet'), + ]) diff --git a/tests/generators/finality/requirements.txt b/tests/generators/finality/requirements.txt new file mode 100644 index 0000000000..b823142988 --- /dev/null +++ b/tests/generators/finality/requirements.txt @@ -0,0 +1,2 @@ +../../core/gen_helpers +../../../ \ No newline at end of file From b4c91efdbc0ff3e4da9d49a0b1a973b927d2cd31 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Jul 2020 04:25:53 +0800 Subject: [PATCH 228/236] Add __init__.py --- .../core/pyspec/eth2spec/test/phase1/epoch_processing/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase1/epoch_processing/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 607041225f44fa76e5fb213d579a5c351e45ab2f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 23 Jul 2020 15:05:25 -0600 Subject: [PATCH 229/236] split config vs constants in deposit-contract spec --- specs/phase0/deposit-contract.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index db95661897..b4f8d30364 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -10,7 +10,7 @@ - [Introduction](#introduction) - [Constants](#constants) - - [Contract](#contract) +- [Configuration](#configuration) - [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) - [`deposit` function](#deposit-function) - [Deposit amount](#deposit-amount) @@ -27,14 +27,23 @@ This document represents the specification for the beacon chain deposit contract ## Constants -### Contract +The following values are (non-configurable) constants used throughout the specification. + +| Name | Value | +| - | - | +| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | + +## Configuration + +*Note*: The default mainnet configuration values are included here for spec-design purposes. +The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory. +These configurations are updated for releases and may be out of sync during `dev` changes. | Name | Value | | - | - | | `DEPOSIT_CHAIN_ID` | `1` | | `DEPOSIT_NETWORK_ID` | `1` | | `DEPOSIT_CONTRACT_ADDRESS` | **TBD** | -| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | ## Ethereum 1.0 deposit contract From 64aed78d6aa4c66f5b3b5378b1db47828371f62f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 24 Jul 2020 05:25:17 +0800 Subject: [PATCH 230/236] Only one test format for finality tests. No extra `finality.md` here --- tests/formats/finality/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/formats/finality/README.md b/tests/formats/finality/README.md index 3e2b40be37..da9108a6aa 100644 --- a/tests/formats/finality/README.md +++ b/tests/formats/finality/README.md @@ -2,7 +2,7 @@ The aim of the tests for the finality rules. -- [`finality`](./finality.md): transitions triggered by one or more blocks. +- `finality`: transitions triggered by one or more blocks. ## Test case format From e9d887be4d23a9aec6e88d499007e5d547e7b01a Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 24 Jul 2020 01:19:14 +0200 Subject: [PATCH 231/236] Enable BLS in finality tests, use Milagro --- tests/core/pyspec/eth2spec/test/conftest.py | 4 ++-- .../test/phase0/finality/test_finality.py | 7 +------ tests/core/pyspec/eth2spec/utils/bls.py | 16 ++++++++++++++++ tests/generators/bls/main.py | 1 + tests/generators/epoch_processing/main.py | 2 ++ tests/generators/finality/main.py | 2 ++ tests/generators/genesis/main.py | 2 ++ tests/generators/operations/main.py | 2 ++ tests/generators/rewards/main.py | 2 ++ tests/generators/sanity/main.py | 2 ++ 10 files changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index 01c974ae0d..21f7c7abbd 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -59,8 +59,8 @@ def bls_default(request): def bls_type(request): bls_type = request.config.getoption("--bls-type") if bls_type == "py_ecc": - bls_utils.bls = bls_utils.py_ecc_bls + bls_utils.use_py_ecc() elif bls_type == "milagro": - bls_utils.bls = bls_utils.milagro_bls + bls_utils.use_milagro() else: raise Exception(f"unrecognized bls type: {bls_type}") diff --git a/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py index adbadcdf2c..d2d3d44050 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py +++ b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import PHASE0, spec_state_test, never_bls, with_all_phases, with_phases +from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases, with_phases from eth2spec.test.helpers.state import next_epoch_via_block from eth2spec.test.helpers.attestations import next_epoch_with_attestations @@ -30,7 +30,6 @@ def check_finality(spec, @with_phases([PHASE0]) @spec_state_test -@never_bls def test_finality_no_updates_at_genesis(spec, state): assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH @@ -54,7 +53,6 @@ def test_finality_no_updates_at_genesis(spec, state): @with_all_phases @spec_state_test -@never_bls def test_finality_rule_4(spec, state): # get past first two epochs that finality does not run on next_epoch_via_block(spec, state) @@ -80,7 +78,6 @@ def test_finality_rule_4(spec, state): @with_all_phases @spec_state_test -@never_bls def test_finality_rule_1(spec, state): # get past first two epochs that finality does not run on next_epoch_via_block(spec, state) @@ -108,7 +105,6 @@ def test_finality_rule_1(spec, state): @with_all_phases @spec_state_test -@never_bls def test_finality_rule_2(spec, state): # get past first two epochs that finality does not run on next_epoch_via_block(spec, state) @@ -138,7 +134,6 @@ def test_finality_rule_2(spec, state): @with_all_phases @spec_state_test -@never_bls def test_finality_rule_3(spec, state): """ Test scenario described here diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 778b23da7e..8b91dd64ee 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -14,6 +14,22 @@ STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE) +def use_milagro(): + """ + Shortcut to use Milagro as BLS library + """ + global bls + bls = milagro_bls + + +def use_py_ecc(): + """ + Shortcut to use Py-ecc as BLS library + """ + global bls + bls = py_ecc_bls + + def only_with_bls(alt_return=None): """ Decorator factory to make a function only run when BLS is active. Otherwise return the default. diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index bfe0c8342a..6fec61de02 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -321,6 +321,7 @@ def cases_fn() -> Iterable[gen_typing.TestCase]: if __name__ == "__main__": + bls.use_py_ecc() # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct. gen_runner.run_generator("bls", [ create_provider('sign', case01_sign), create_provider('verify', case02_verify), diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py index 13ac76f419..418d6c7505 100644 --- a/tests/generators/epoch_processing/main.py +++ b/tests/generators/epoch_processing/main.py @@ -14,6 +14,7 @@ from importlib import reload from eth2spec.config import config_util from eth2spec.test.context import PHASE0 +from eth2spec.utils import bls def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: @@ -22,6 +23,7 @@ def prepare_fn(configs_path: str) -> str: config_util.prepare_config(configs_path, config_name) reload(spec_phase0) reload(spec_phase1) + bls.use_milagro() return config_name def cases_fn() -> Iterable[gen_typing.TestCase]: diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py index 5ab5a98e1a..dca0ecb8dc 100644 --- a/tests/generators/finality/main.py +++ b/tests/generators/finality/main.py @@ -9,6 +9,7 @@ from eth2spec.config import config_util from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.utils import bls def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: @@ -17,6 +18,7 @@ def prepare_fn(configs_path: str) -> str: config_util.prepare_config(configs_path, config_name) reload(spec_phase0) reload(spec_phase1) + bls.use_milagro() return config_name def cases_fn() -> Iterable[gen_typing.TestCase]: diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py index a978ab6849..ce055b44ac 100644 --- a/tests/generators/genesis/main.py +++ b/tests/generators/genesis/main.py @@ -8,6 +8,7 @@ from eth2spec.phase0 import spec as spec from importlib import reload from eth2spec.config import config_util +from eth2spec.utils import bls def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: @@ -15,6 +16,7 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin def prepare_fn(configs_path: str) -> str: config_util.prepare_config(configs_path, config_name) reload(spec) + bls.use_milagro() return config_name def cases_fn() -> Iterable[gen_typing.TestCase]: diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index 6d4f6d1395..be490c5b2e 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -16,6 +16,7 @@ from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 from eth2spec.test.context import PHASE0 +from eth2spec.utils import bls def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: @@ -24,6 +25,7 @@ def prepare_fn(configs_path: str) -> str: config_util.prepare_config(configs_path, config_name) reload(spec_phase0) reload(spec_phase1) + bls.use_milagro() return config_name def cases_fn() -> Iterable[gen_typing.TestCase]: diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index 5d063c4340..dd82ee165a 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -12,6 +12,7 @@ from importlib import reload from eth2spec.config import config_util from eth2spec.test.context import PHASE0 +from eth2spec.utils import bls def create_provider(tests_src, config_name: str) -> gen_typing.TestProvider: @@ -20,6 +21,7 @@ def prepare_fn(configs_path: str) -> str: config_util.prepare_config(configs_path, config_name) reload(spec_phase0) reload(spec_phase1) + bls.use_milagro() return config_name def cases_fn() -> Iterable[gen_typing.TestCase]: diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 45a1c8c4f5..2feaaf09dd 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -9,6 +9,7 @@ from eth2spec.config import config_util from eth2spec.phase0 import spec as spec_phase0 from eth2spec.phase1 import spec as spec_phase1 +from eth2spec.utils import bls def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider: @@ -17,6 +18,7 @@ def prepare_fn(configs_path: str) -> str: config_util.prepare_config(configs_path, config_name) reload(spec_phase0) reload(spec_phase1) + bls.use_milagro() return config_name def cases_fn() -> Iterable[gen_typing.TestCase]: From 230e5ce34598cf0e86d23d34cd4f733ba92fbfae Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 24 Jul 2020 01:19:50 +0200 Subject: [PATCH 232/236] remove outdated hash cache, use less memory --- setup.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/setup.py b/setup.py index bac58e16a7..d1f7b35cea 100644 --- a/setup.py +++ b/setup.py @@ -141,11 +141,6 @@ def ceillog2(x: uint64) -> int: return (x - 1).bit_length() ''' PHASE0_SUNDRY_FUNCTIONS = ''' -# Monkey patch hash cache -_hash = hash -hash_cache: Dict[bytes, Bytes32] = {} - - def get_eth1_data(block: Eth1Block) -> Eth1Data: """ A stub function return mocking Eth1Data. @@ -156,12 +151,6 @@ def get_eth1_data(block: Eth1Block) -> Eth1Data: block_hash=hash_tree_root(block)) -def hash(x: bytes) -> Bytes32: # type: ignore - if x not in hash_cache: - hash_cache[x] = Bytes32(_hash(x)) - return hash_cache[x] - - def cache_this(key_fn, value_fn, lru_size): # type: ignore cache_dict = LRU(size=lru_size) From 1d2a47566cd5372a7ae2233e6ae4f158b6264ddf Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 23 Jul 2020 16:35:21 -0700 Subject: [PATCH 233/236] Update sharding FAQs link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13c644a778..78ce5f31f8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Join the chat at https://discord.gg/hpFs23p](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/hpFs23p) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQ) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). +To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). This repository hosts the current Eth2 specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. From 951c552ecccce805d09ebd50327b06accecbb97d Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 24 Jul 2020 01:53:14 +0200 Subject: [PATCH 234/236] remove second hash cache, type as Bytes32 --- .../core/pyspec/eth2spec/utils/hash_function.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/core/pyspec/eth2spec/utils/hash_function.py b/tests/core/pyspec/eth2spec/utils/hash_function.py index 627f9b9904..470cb1da91 100644 --- a/tests/core/pyspec/eth2spec/utils/hash_function.py +++ b/tests/core/pyspec/eth2spec/utils/hash_function.py @@ -1,17 +1,9 @@ from hashlib import sha256 -from typing import Dict, Union +from remerkleable.byte_arrays import Bytes32 +from typing import Union ZERO_BYTES32 = b'\x00' * 32 -def _hash(x: Union[bytes, bytearray, memoryview]) -> bytes: - return sha256(x).digest() - - -hash_cache: Dict[bytes, bytes] = {} - - -def hash(x: bytes) -> bytes: - if x in hash_cache: - return hash_cache[x] - return _hash(x) +def hash(x: Union[bytes, bytearray, memoryview]) -> Bytes32: + return Bytes32(sha256(x).digest()) From 6f7652d330f5c0db698deb994e5f242e35a43f68 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Fri, 24 Jul 2020 07:26:37 +0200 Subject: [PATCH 235/236] process_attestation: Validate epoch before using it `data.target.epoch` is used to count the active validator set. Because `get_committee_count_per_slot` is extremely inefficient the way the spec is written, clients cache it, or the underlying active validator set. Performing the checks in the given order leads to a (very unlikely) security issue where the a cold and above all, distant value may get used which may be costly - reordering the checks brings the value into a more reasonable range before using it. --- specs/phase0/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 4db18228ce..15c8924a4e 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1718,10 +1718,10 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: data = attestation.data - assert data.index < get_committee_count_per_slot(state, data.target.epoch) assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) assert data.target.epoch == compute_epoch_at_slot(data.slot) assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH + assert data.index < get_committee_count_per_slot(state, data.target.epoch) committee = get_beacon_committee(state, data.slot, data.index) assert len(attestation.aggregation_bits) == len(committee) From fbb8401d03f0b61deb3acfa2aa802e2732355304 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 24 Jul 2020 15:09:20 +0200 Subject: [PATCH 236/236] sanity test: proposer slashes themselves --- .../test/phase0/sanity/test_blocks.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index c3b1dceed8..c183865a52 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -313,6 +313,28 @@ def test_empty_epoch_transition_not_finalizing(spec, state): assert state.balances[index] < pre_balances[index] +@with_all_phases +@spec_state_test +def test_proposer_self_slashing(spec, state): + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + assert not state.validators[block.proposer_index].slashed + + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=block.proposer_index, signed_1=True, signed_2=True) + block.body.proposer_slashings.append(proposer_slashing) + + # The header is processed *before* the block body: + # the proposer was not slashed before the body, thus the block is valid. + signed_block = state_transition_and_sign_block(spec, state, block) + # The proposer slashed themselves. + assert state.validators[block.proposer_index].slashed + + yield 'blocks', [signed_block] + yield 'post', state + + @with_all_phases @spec_state_test def test_proposer_slashing(spec, state):