Merge pull request #1704 from ethereum/phase1-validator
phase 1 validator guide (round 1)
This commit is contained in:
commit
8e30ee55d6
|
@ -55,7 +55,8 @@ DOMAIN_SHARD_COMMITTEE: 0x81000000
|
|||
DOMAIN_LIGHT_CLIENT: 0x82000000
|
||||
# custody-game spec
|
||||
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
|
||||
|
||||
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
|
||||
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
|
||||
|
||||
# custody-game
|
||||
# ---------------------------------------------------------------
|
||||
|
|
|
@ -57,7 +57,8 @@ DOMAIN_SHARD_COMMITTEE: 0x81000000
|
|||
DOMAIN_LIGHT_CLIENT: 0x82000000
|
||||
# custody-game spec
|
||||
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
|
||||
|
||||
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
|
||||
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
|
||||
|
||||
# custody-game
|
||||
# ---------------------------------------------------------------
|
||||
|
|
1
setup.py
1
setup.py
|
@ -395,6 +395,7 @@ class PySpecCommand(Command):
|
|||
specs/phase1/fork-choice.md
|
||||
specs/phase1/phase1-fork.md
|
||||
specs/phase1/shard-fork-choice.md
|
||||
specs/phase1/validator.md
|
||||
"""
|
||||
else:
|
||||
raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)
|
||||
|
|
|
@ -145,9 +145,12 @@ Configuration is not namespaced. Instead it is strictly an extension;
|
|||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` |
|
||||
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` |
|
||||
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` |
|
||||
| `DOMAIN_SHARD_PROPOSAL` | `DomainType('0x80000000')` |
|
||||
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` |
|
||||
| `DOMAIN_LIGHT_CLIENT` | `DomainType('0x82000000')` |
|
||||
| `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType('0x83000000')` |
|
||||
| `DOMAIN_LIGHT_SELECTION_PROOF` | `DomainType('0x84000000')` |
|
||||
| `DOMAIN_LIGHT_AGGREGATE_AND_PROOF` | `DomainType('0x85000000')` |
|
||||
|
||||
## Updated containers
|
||||
|
||||
|
@ -257,7 +260,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
|
||||
```
|
||||
|
||||
|
@ -376,7 +379,6 @@ class ShardBlockHeader(Container):
|
|||
class ShardState(Container):
|
||||
slot: Slot
|
||||
gasprice: Gwei
|
||||
transition_digest: Bytes32
|
||||
latest_block_root: Root
|
||||
```
|
||||
|
||||
|
@ -718,7 +720,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
|
|||
process_block_header(state, block)
|
||||
process_randao(state, block.body)
|
||||
process_eth1_data(state, block.body)
|
||||
process_light_client_signatures(state, block.body)
|
||||
process_light_client_aggregate(state, block.body)
|
||||
process_operations(state, block.body)
|
||||
```
|
||||
|
||||
|
@ -848,6 +850,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
|
||||
|
||||
|
@ -999,21 +1004,24 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato
|
|||
#### 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 = compute_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 = compute_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 optional_fast_aggregate_verify(signer_pubkeys, signing_root, block_body.light_client_signature)
|
||||
```
|
||||
|
||||
|
|
|
@ -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)
|
||||
- [`CustodyChunkChallenge`](#custodychunkchallenge)
|
||||
|
@ -93,14 +92,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
|
||||
|
|
|
@ -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)
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,14 +10,10 @@
|
|||
|
||||
- [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)
|
||||
- [Verifying the proof](#verifying-the-proof)
|
||||
- [Honest committee member behavior](#honest-committee-member-behavior)
|
||||
- [Helper functions](#helper-functions-1)
|
||||
- [Make attestations](#make-attestations)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
|
@ -27,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
|
||||
|
@ -80,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
|
||||
|
@ -94,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
|
||||
```
|
||||
|
||||
|
@ -154,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
|
||||
|
@ -166,106 +141,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_at_slot(beacon_state: BeaconState,
|
||||
shard_parent_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.
|
||||
"""
|
||||
shard_blocks = [block for block in shard_blocks if block.message.slot == slot]
|
||||
if len(shard_blocks) == 0:
|
||||
block = ShardBlock(slot=slot, shard=shard)
|
||||
proposal = SignedShardBlock(message=block)
|
||||
elif len(shard_blocks) == 1:
|
||||
proposal = shard_blocks[0]
|
||||
else:
|
||||
proposal = get_winning_proposal(beacon_state, shard_blocks)
|
||||
|
||||
# Apply state transition
|
||||
shard_state = get_post_shard_state(beacon_state, shard_parent_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]
|
||||
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1))
|
||||
for slot in offset_slots:
|
||||
proposal, shard_state = get_proposal_at_slot(
|
||||
beacon_state=beacon_state,
|
||||
shard_parent_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 = compute_offset_slots(get_latest_slot_for_shard(beacon_state, shard), Slot(beacon_state.slot + 1))
|
||||
proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks)
|
||||
|
||||
shard_block_lengths = []
|
||||
proposer_signatures = []
|
||||
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,
|
||||
)
|
||||
```
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Ethereum 2.0 Phase 1 -- Updates to honest validator
|
||||
# Ethereum 2.0 Phase 0 -- Honest Validator
|
||||
|
||||
**Notice**: This document is a work-in-progress for researchers and implementers. This is so far only a skeleton that describes non-obvious pitfalls so that they won't be forgotten when the full version of the document is prepared
|
||||
**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
|
||||
|
||||
|
@ -10,6 +10,48 @@
|
|||
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Constants](#constants)
|
||||
- [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)
|
||||
- [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)
|
||||
- [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)
|
||||
- [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-1)
|
||||
- [`LightAggregateAndProof`](#lightaggregateandproof)
|
||||
- [`SignedLightAggregateAndProof`](#signedlightaggregateandproof)
|
||||
- [How to avoid slashing](#how-to-avoid-slashing)
|
||||
- [Custody slashing](#custody-slashing)
|
||||
|
||||
|
@ -18,22 +60,494 @@
|
|||
|
||||
## Introduction
|
||||
|
||||
This is an update to the [Phase 0 -- Honest validator](../phase0/validator.md) honest validator guide. This will only describe the differences in phase 1. All behaviours in phase 0 remain valid
|
||||
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).
|
||||
|
||||
### Misc
|
||||
|
||||
| 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
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
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}_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.
|
||||
|
||||
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`
|
||||
|
||||
`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_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]`
|
||||
|
||||
*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,
|
||||
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, state.slot)
|
||||
for committee_index in map(CommitteeIndex, range(committee_count)):
|
||||
shard = compute_shard_from_committee_index(state, committee_index, state.slot)
|
||||
# All attestations in the block for this committee/shard and are "on time"
|
||||
shard_attestations = [
|
||||
attestation for attestation in attestations
|
||||
if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index
|
||||
]
|
||||
committee = get_beacon_committee(state, state.slot, committee_index)
|
||||
|
||||
# Loop over all shard transition roots, looking for a winning root
|
||||
shard_transition_roots = set([a.data.shard_transition_root for a in shard_attestations])
|
||||
for shard_transition_root in sorted(shard_transition_roots):
|
||||
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)
|
||||
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)
|
||||
winning_roots.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 get_best_light_client_aggregate(block: BeaconBlock,
|
||||
aggregates: Sequence[LightClientVote]) -> LightClientVote:
|
||||
viable_aggregates = [
|
||||
aggregate for aggregate in aggregates
|
||||
if aggregate.slot == compute_previous_slot(block.slot) and aggregate.beacon_block_root == block.parent_root
|
||||
]
|
||||
|
||||
return max(
|
||||
viable_aggregates,
|
||||
# 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(),
|
||||
)
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
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
|
||||
shard_head_root: Root
|
||||
# Full shard transition
|
||||
shard_transition: ShardTransition
|
||||
```
|
||||
|
||||
#### `FullAttestation`
|
||||
|
||||
```python
|
||||
class FullAttestation(Container):
|
||||
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
|
||||
data: FullAttestationData
|
||||
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 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
|
||||
|
||||
`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 `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` (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.
|
||||
|
||||
##### Head shard root
|
||||
|
||||
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_transition_fields(
|
||||
beacon_state: BeaconState,
|
||||
shard: Shard,
|
||||
shard_blocks: Sequence[SignedShardBlock],
|
||||
validate_signature: bool=True,
|
||||
) -> Tuple[Sequence[uint64], Sequence[Root], Sequence[ShardState]]:
|
||||
shard_states = []
|
||||
shard_data_roots = []
|
||||
shard_block_lengths = []
|
||||
|
||||
shard_state = beacon_state.shard_states[shard]
|
||||
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),
|
||||
Slot(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=shard))
|
||||
shard_data_roots.append(Root())
|
||||
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))
|
||||
|
||||
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 = compute_offset_slots(
|
||||
get_latest_slot_for_shard(beacon_state, shard),
|
||||
Slot(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:
|
||||
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
|
||||
|
||||
Next, the validator creates `attestation`, a `FullAttestation` as defined above.
|
||||
|
||||
`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
|
||||
|
||||
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.
|
||||
|
||||
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) -> bool:
|
||||
next_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD)
|
||||
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: LightClientVote,
|
||||
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_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)
|
||||
```
|
||||
|
||||
##### `LightAggregateAndProof`
|
||||
|
||||
```python
|
||||
class LightAggregateAndProof(Container):
|
||||
aggregator_index: ValidatorIndex
|
||||
aggregate: LightClientVote
|
||||
selection_proof: BLSSignature
|
||||
```
|
||||
|
||||
##### `SignedLightAggregateAndProof`
|
||||
|
||||
```python
|
||||
class SignedLightAggregateAndProof(Container):
|
||||
message: LightAggregateAndProof
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
## How to avoid slashing
|
||||
|
||||
Proposer and Attester slashings described in Phase 0 remain in place with the
|
||||
addition of the following.
|
||||
|
||||
### Custody slashing
|
||||
|
||||
To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret:
|
||||
|
||||
```python
|
||||
def get_custody_secret(spec, state, validator_index, epoch=None):
|
||||
period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None
|
||||
else spec.get_current_epoch(state))
|
||||
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index)
|
||||
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
|
||||
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
|
||||
return bls.Sign(privkeys[validator_index], signing_root)
|
||||
def get_custody_secret(state: BeaconState,
|
||||
validator_index: ValidatorIndex,
|
||||
privkey: int,
|
||||
epoch: Epoch=None) -> BLSSignature:
|
||||
if epoch is None:
|
||||
epoch = get_current_epoch(state)
|
||||
period = get_custody_period_for_validator(validator_index, epoch)
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(period, validator_index)
|
||||
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
|
||||
signing_root = compute_signing_root(Epoch(epoch_to_sign), domain)
|
||||
return bls.Sign(privkey, signing_root)
|
||||
```
|
||||
|
||||
Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated. While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing.
|
||||
|
|
|
@ -91,7 +91,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
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ def get_shard_transitions(spec, parent_beacon_state, shard_blocks):
|
|||
shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
|
||||
on_time_slot = parent_beacon_state.slot + 1
|
||||
for shard, blocks in shard_blocks.items():
|
||||
shard_transition = spec.get_shard_transition(parent_beacon_state, shard, blocks)
|
||||
offset_slots = spec.compute_offset_slots(
|
||||
spec.get_latest_slot_for_shard(parent_beacon_state, shard),
|
||||
on_time_slot,
|
||||
|
|
|
@ -15,15 +15,19 @@ from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard
|
|||
def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True):
|
||||
state = transition_to_valid_shard_slot(spec, state)
|
||||
committee_index = spec.CommitteeIndex(0)
|
||||
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
|
||||
assert state.shard_states[shard].slot == state.slot - 1
|
||||
transition_to(spec, state, state.slot + target_len_offset_slot)
|
||||
assert state.shard_states[shard].slot == state.slot - target_len_offset_slot - 1
|
||||
init_slot = state.slot
|
||||
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, slot=state.slot, signed=True)
|
||||
shard_blocks = [shard_block]
|
||||
|
||||
# Transition state latest shard slot
|
||||
transition_to(spec, state, shard_slot)
|
||||
# Create a shard_transitions that would be included at beacon block `state.slot + target_len_offset_slot`
|
||||
shard_transitions = get_shard_transitions(
|
||||
spec,
|
||||
state,
|
||||
|
@ -39,6 +43,8 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True):
|
|||
)
|
||||
next_slot(spec, state)
|
||||
pre_gasprice = state.shard_states[shard].gasprice
|
||||
|
||||
transition_to(spec, state, init_slot + target_len_offset_slot)
|
||||
pre_shard_state = state.shard_states[shard]
|
||||
yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid)
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from eth2spec.test.context import spec_state_test, always_bls, with_all_phases
|
||||
from eth2spec.test.helpers.attestations import build_attestation_data
|
||||
from eth2spec.test.context import (
|
||||
spec_state_test,
|
||||
always_bls, with_phases, with_all_phases,
|
||||
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
|
||||
|
@ -320,15 +324,16 @@ def test_get_block_signature(spec, state):
|
|||
@with_all_phases
|
||||
@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,
|
||||
|
@ -363,7 +368,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 +382,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):
|
||||
|
|
Loading…
Reference in New Issue