From d789f3d32d67ce1cd811dfcc403e5fb76bc256d9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 1 Apr 2020 12:02:56 -0600 Subject: [PATCH 01/17] 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 9c0cd0a6c..135e2d3d3 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 000000000..600f05c17 --- /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 02/17] 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 135e2d3d3..bca952a92 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 600f05c17..acd98ee5d 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 03/17] 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 911eb65b0..28179bfce 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,7 @@ SSZObject = TypeVar('SSZObject', bound=View) 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 @@ class PySpecCommand(Command): 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 acd98ee5d..fd8787adf 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 04/17] 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 fd8787adf..a26987a34 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 74204f795d72a4b0322cac5e2ee5a4ea7ebc257a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 3 Jun 2020 12:16:39 -0600 Subject: [PATCH 05/17] 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 0af51a815..1fabe0370 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 e6221a980..ec764f7b2 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 a26987a34..c5d1cd868 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 1dfa0e4d0..26affd579 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 52de25048a20e79ee618396af17f4277c6347f2f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 09:35:09 -0600 Subject: [PATCH 06/17] 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 c5d1cd868..243c21014 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 8e5c98ef3c661aa0b10c5ab4e6b050bb455c0957 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 12:01:53 -0600 Subject: [PATCH 07/17] 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 243c21014..21f9c600a 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 08/17] 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 21f9c600a..935f34efb 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 7f680dfca4ad4c5085859ee4cb380fb7971ee5d9 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 5 Jun 2020 14:06:39 -0600 Subject: [PATCH 09/17] 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 1372b0654..48930b95d 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 58efada83..20e9ca3b5 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 abb5e7278..544d88523 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 00ffbe0a8..959eaa2d8 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 dacf86a5c0ab75bd966f9a3639607427cd876dd3 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 10 Jun 2020 02:43:59 +0800 Subject: [PATCH 10/17] 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 d559ebcbd..8ee6a1e83 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 d362ed633..7a7423e10 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 0607613e8..411ad9b6b 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 ea19b813d..24c39aa3d 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 935f34efb..403a6efb3 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 11/17] 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 1e0560405..2bfe63bd1 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 15443e386..ddf66f6c2 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 4b8f132957e6e6b5f197b0a9fafe8064fd32f072 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 10 Jun 2020 11:32:02 -0500 Subject: [PATCH 12/17] 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 935f34efb..9c6198989 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 f62125eaa685b6413dbf99ad60b9c5a86fb82afe Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 15 Jun 2020 06:54:48 -0600 Subject: [PATCH 13/17] 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 9c6198989..3048cf8b7 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 14/17] 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 cddbb8dfe..08cf2317d 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 556ff2618..c519a7427 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 77cc015c8..dcb02bfb4 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 5f5acd84f..9f89c336d 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 3048cf8b7..65510cc14 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 15/17] 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 dcb02bfb4..436404a05 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 2e0950560b7759024d2cf7239b112698d8b147a5 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Jun 2020 16:03:36 -0600 Subject: [PATCH 16/17] 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 0607613e8..e051054fe 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 65510cc14..6db20e82e 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 15443e386..ddf66f6c2 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 fbf10a0db3155ae413248a9241104c4755fd7db4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 16 Jun 2020 22:29:28 -0600 Subject: [PATCH 17/17] 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 aef8fbb99..558a1b3bf 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 26affd579..8edd4fd2a 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