Attestation changes + persistent committee changes (#1294)

* Minimal attestation simplification

* minor fix

* Make the tests pass

* Decrease `PLACEHOLDER`, Use `compute_epoch_of_shard_slot`

* Fix proposer signature name and use get_seed() to calculate current_shuffling_seed

* Fix linter error

* Add the WIP `test_is_valid_shard_block`

* Add `get_shard_block_attester_committee`

* Simplified committee selection

* Added some helpers and simplified

* Update specs/core/1_shard-data-chains.md

* Update 1_shard-data-chains.md

* Simplified switchover epochs, changed block structure, changed crosslink structure

* Update 1_shard-data-chains.md

* Moved balance dependency to proposer selection

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Danny Ryan <dannyjryan@gmail.com>

* Update specs/core/1_shard-data-chains.md

* Fixed shard header flattening

* Update specs/core/1_shard-data-chains.md

* Minor fixes

* Update specs/core/1_shard-data-chains.md

* Update specs/core/1_shard-data-chains.md

Co-Authored-By: Hsiao-Wei Wang <hwwang156@gmail.com>

* cleanup testing and lint

* return none if not active validators in persistent committee

* only allow active validators as shard proposer
This commit is contained in:
vbuterin 2019-07-29 09:47:35 -04:00 committed by GitHub
parent 6a9e782fff
commit de9b4f2d6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 263 additions and 193 deletions

View File

@ -48,11 +48,10 @@ from dataclasses import (
from eth2spec.utils.ssz.ssz_impl import ( from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root, hash_tree_root,
signing_root, signing_root,
serialize,
is_empty, is_empty,
) )
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
bit, boolean, Container, List, Vector, Bytes, uint64, uint64, bit, boolean, Container, List, Vector, Bytes, BytesN,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
) )
from eth2spec.utils.bls import ( from eth2spec.utils.bls import (

View File

@ -9,6 +9,7 @@
- [Ethereum 2.0 Phase 1 -- Shard Data Chains](#ethereum-20-phase-1----shard-data-chains) - [Ethereum 2.0 Phase 1 -- Shard Data Chains](#ethereum-20-phase-1----shard-data-chains)
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Introduction](#introduction) - [Introduction](#introduction)
- [Custom types](#custom-types)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Misc](#misc) - [Misc](#misc)
- [Initial values](#initial-values) - [Initial values](#initial-values)
@ -16,21 +17,25 @@
- [Signature domain types](#signature-domain-types) - [Signature domain types](#signature-domain-types)
- [TODO PLACEHOLDER](#todo-placeholder) - [TODO PLACEHOLDER](#todo-placeholder)
- [Data structures](#data-structures) - [Data structures](#data-structures)
- [`ShardBlockBody`](#shardblockbody)
- [`ShardAttestation`](#shardattestation)
- [`ShardBlock`](#shardblock)
- [`ShardBlockHeader`](#shardblockheader) - [`ShardBlockHeader`](#shardblockheader)
- [`ShardBlock`](#shardblock)
- [`ShardBlockSignatures`](#shardblocksignatures)
- [`ShardBlockCore`](#shardblockcore)
- [`ExtendedShardBlockCore`](#extendedshardblockcore)
- [Helper functions](#helper-functions) - [Helper functions](#helper-functions)
- [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot)
- [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot)
- [`get_shard_period_start_epoch`](#get_shard_period_start_epoch)
- [`get_period_committee`](#get_period_committee) - [`get_period_committee`](#get_period_committee)
- [`get_switchover_epoch`](#get_switchover_epoch)
- [`get_persistent_committee`](#get_persistent_committee) - [`get_persistent_committee`](#get_persistent_committee)
- [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_shard_block_proposer_index`](#get_shard_block_proposer_index)
- [`get_shard_block_attester_committee`](#get_shard_block_attester_committee)
- [`get_shard_header`](#get_shard_header) - [`get_shard_header`](#get_shard_header)
- [`verify_shard_attestation_signature`](#verify_shard_attestation_signature) - [`pad`](#pad)
- [`flatten_shard_header`](#flatten_shard_header)
- [`compute_crosslink_data_root`](#compute_crosslink_data_root) - [`compute_crosslink_data_root`](#compute_crosslink_data_root)
- [Object validity](#object-validity) - [Object validity](#object-validity)
- [Shard blocks](#shard-blocks) - [Shard blocks](#shard-blocks)
- [Shard attestations](#shard-attestations)
- [Beacon attestations](#beacon-attestations) - [Beacon attestations](#beacon-attestations)
- [Shard fork choice rule](#shard-fork-choice-rule) - [Shard fork choice rule](#shard-fork-choice-rule)
@ -40,14 +45,24 @@
This document describes the shard data layer and the shard fork choice rule in Phase 1 of Ethereum 2.0. This document describes the shard data layer and the shard fork choice rule in Phase 1 of Ethereum 2.0.
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `ShardSlot` | `uint64` | a slot number in shard chain |
## Configuration ## Configuration
### Misc ### Misc
| Name | Value | | Name | Value |
| - | - | | - | - |
| `BYTES_PER_SHARD_BLOCK_BODY` | `2**14` (= 16,384) | | `SHARD_HEADER_SIZE` | `2**9` (= 512) |
| `MAX_SHARD_ATTESTIONS` | `2**4` (= 16) | | `SHARD_BLOCK_SIZE_LIMIT` | `2**16` (= 65,536) |
| `SHARD_SLOTS_PER_BEACON_SLOT` | `2**1` (= 2) |
| `MAX_PERSISTENT_COMMITTEE_SIZE` | `2**7` (= 128) |
### Initial values ### Initial values
@ -61,7 +76,8 @@ This document describes the shard data layer and the shard fork choice rule in P
| Name | Value | Unit | Duration | | Name | Value | Unit | Duration |
| - | - | :-: | :-: | | - | - | :-: | :-: |
| `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.2 minutes | | `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.4 minutes |
| `EPOCHS_PER_SHARD_PERIOD` | `2**8` (= 256) | epochs | ~27 hours |
### Signature domain types ### Signature domain types
@ -76,85 +92,102 @@ The following types are defined, mapping into `DomainType` (little endian):
| Name | Value | | Name | Value |
| - | - | | - | - |
| `PLACEHOLDER` | `2**32` | | `PLACEHOLDER` | `2**3` |
## Data structures ## Data structures
### `ShardBlockBody` _Note: the shard block header structure is carefully designed so that all of the values have the same depth in a hash tree implementation, so `hash_tree_root(SSZ_partial(x)) == hash_tree_root(x)` (using the "left-to-right leaves" scheme [here](https://github.com/ethereum/eth2.0-specs/issues/1303)), which allows shard block headers to look like an SSZ object when in the crosslink structure. This is done by balancing it so that 7 or 8 items are on the left side (the "core") and two 96-byte (ie. 3*2 = 6 chunk) items are on the right side. Change with care._
### `ShardBlockHeader`
```python ```python
class ShardBlockBody(Container): class ShardBlockHeader(Container):
data: Vector[Bytes[PLACEHOLDER], BYTES_PER_SHARD_BLOCK_BODY] core: ShardBlockCore
``` signatures: ShardBlockSignatures
### `ShardAttestation`
```python
class ShardAttestation(Container):
class data(Container):
slot: Slot
shard: Shard
shard_block_root: Hash
aggregation_bits: Bitlist[PLACEHOLDER]
aggregate_signature: BLSSignature
``` ```
### `ShardBlock` ### `ShardBlock`
```python ```python
class ShardBlock(Container): class ShardBlock(Container):
slot: Slot core: ExtendedShardBlockCore
shard: Shard signatures: ShardBlockSignatures
beacon_chain_root: Hash
parent_root: Hash
data: ShardBlockBody
state_root: Hash
attestations: List[ShardAttestation, PLACEHOLDER]
signature: BLSSignature
``` ```
### `ShardBlockHeader` ### `ShardBlockSignatures`
```python ```python
class ShardBlockHeader(Container): class ShardBlockSignatures(Container):
slot: Slot attestation_signature: BLSSignature
shard: Shard proposer_signature: BLSSignature
```
### `ShardBlockCore`
```python
class ShardBlockCore(Container):
slot: ShardSlot
beacon_chain_root: Hash beacon_chain_root: Hash
parent_root: Hash parent_root: Hash
body_root: Hash data_root: Hash
state_root: Hash state_root: Hash
attestations: List[ShardAttestation, PLACEHOLDER] total_bytes: uint64
signature: BLSSignature attester_bitfield: Bitvector[MAX_PERSISTENT_COMMITTEE_SIZE * 2]
```
### `ExtendedShardBlockCore`
```python
class ExtendedShardBlockCore(Container):
slot: ShardSlot
beacon_chain_root: Hash
parent_root: Hash
data: Bytes[SHARD_BLOCK_SIZE_LIMIT - SHARD_HEADER_SIZE]
state_root: Hash
total_bytes: uint64
attester_bitfield: Bitvector[MAX_PERSISTENT_COMMITTEE_SIZE * 2]
``` ```
## Helper functions ## Helper functions
### `compute_slot_of_shard_slot`
```python
def compute_slot_of_shard_slot(slot: ShardSlot) -> Epoch:
return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT)
```
### `compute_epoch_of_shard_slot`
```python
def compute_epoch_of_shard_slot(slot: ShardSlot) -> Epoch:
return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT // SLOTS_PER_EPOCH)
```
### `get_shard_period_start_epoch`
```python
def get_shard_period_start_epoch(epoch: Epoch, lookback: Epoch=Epoch(0)) -> Epoch:
return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD)
```
### `get_period_committee` ### `get_period_committee`
```python ```python
def get_period_committee(state: BeaconState, def get_period_committee(state: BeaconState,
epoch: Epoch, epoch: Epoch,
shard: Shard, shard: Shard) -> List[ValidatorIndex, MAX_PERSISTENT_COMMITTEE_SIZE]:
index: uint64,
count: uint64) -> Sequence[ValidatorIndex]:
""" """
Return committee for a period. Used to construct persistent committees. Return committee for a period. Used to construct persistent committees.
""" """
return compute_committee( full_committee = compute_committee(
indices=get_active_validator_indices(state, epoch), indices=get_active_validator_indices(state, epoch),
seed=get_seed(state, epoch), seed=get_seed(state, epoch),
index=shard * count + index, index=shard,
count=SHARD_COUNT * count, count=SHARD_COUNT,
) )
```
### `get_switchover_epoch` return full_committee[:MAX_PERSISTENT_COMMITTEE_SIZE]
```python
def get_switchover_epoch(state: BeaconState, epoch: Epoch, index: ValidatorIndex) -> int:
earlier_start_epoch = Epoch(epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD * 2)
return (bytes_to_int(hash(get_seed(state, earlier_start_epoch) + int_to_bytes(index, length=3)[0:8]))
% PERSISTENT_COMMITTEE_PERIOD)
``` ```
### `get_persistent_committee` ### `get_persistent_committee`
@ -162,52 +195,47 @@ def get_switchover_epoch(state: BeaconState, epoch: Epoch, index: ValidatorIndex
```python ```python
def get_persistent_committee(state: BeaconState, def get_persistent_committee(state: BeaconState,
shard: Shard, shard: Shard,
slot: Slot) -> Sequence[ValidatorIndex]: slot: ShardSlot) -> Sequence[ValidatorIndex]:
""" """
Return the persistent committee for the given ``shard`` at the given ``slot``. Return the persistent committee for the given ``shard`` at the given ``slot``.
""" """
epoch = compute_epoch_of_slot(slot) epoch = compute_epoch_of_shard_slot(slot)
earlier_start_epoch = Epoch(epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD * 2)
later_start_epoch = Epoch(epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD)
committee_count = max( earlier_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=Epoch(2)), shard)
len(get_active_validator_indices(state, earlier_start_epoch)) // later_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=Epoch(1)), shard)
(SHARD_COUNT * TARGET_COMMITTEE_SIZE),
len(get_active_validator_indices(state, later_start_epoch)) //
(SHARD_COUNT * TARGET_COMMITTEE_SIZE),
) + 1
index = slot % committee_count
earlier_committee = get_period_committee(state, earlier_start_epoch, shard, index, committee_count)
later_committee = get_period_committee(state, later_start_epoch, shard, index, committee_count)
# Take not-yet-cycled-out validators from earlier committee and already-cycled-in validators from # Take not-yet-cycled-out validators from earlier committee and already-cycled-in validators from
# later committee; return a sorted list of the union of the two, deduplicated # later committee; return a sorted list of the union of the two, deduplicated
return sorted(list(set( return sorted(set(
[i for i in earlier_committee if epoch % PERSISTENT_COMMITTEE_PERIOD < get_switchover_epoch(state, epoch, i)] [i for i in earlier_committee if epoch % EPOCHS_PER_SHARD_PERIOD < i % EPOCHS_PER_SHARD_PERIOD]
+ [i for i in later_committee if epoch % PERSISTENT_COMMITTEE_PERIOD >= get_switchover_epoch(state, epoch, i)] + [i for i in later_committee if epoch % EPOCHS_PER_SHARD_PERIOD >= i % EPOCHS_PER_SHARD_PERIOD]
))) ))
``` ```
### `get_shard_proposer_index` ### `get_shard_block_proposer_index`
```python ```python
def get_shard_proposer_index(state: BeaconState, def get_shard_block_proposer_index(state: BeaconState,
shard: Shard, shard: Shard,
slot: Slot) -> Optional[ValidatorIndex]: slot: ShardSlot) -> Optional[ValidatorIndex]:
# Randomly shift persistent committee # Randomly shift persistent committee
persistent_committee = list(get_persistent_committee(state, shard, slot)) persistent_committee = list(get_persistent_committee(state, shard, slot))
seed = hash(state.current_shuffling_seed + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8)) current_epoch = get_current_epoch(state)
random_index = bytes_to_int(seed[0:8]) % len(persistent_committee)
persistent_committee = persistent_committee[random_index:] + persistent_committee[:random_index]
# Search for an active proposer active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], current_epoch)]
for index in persistent_committee: if not any(active_indices):
if is_active_validator(state.validators[index], get_current_epoch(state)): return None
return index
# No block can be proposed if no validator is active MAX_RANDOM_BYTE = 2**8 - 1
return None seed = hash(get_seed(state, current_epoch) + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8))
i = 0
while True:
candidate_index = active_indices[(slot + i) % len(active_indices)]
random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32]
effective_balance = state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
return ValidatorIndex(candidate_index)
i += 1
``` ```
### `get_shard_header` ### `get_shard_header`
@ -215,35 +243,49 @@ def get_shard_proposer_index(state: BeaconState,
```python ```python
def get_shard_header(block: ShardBlock) -> ShardBlockHeader: def get_shard_header(block: ShardBlock) -> ShardBlockHeader:
return ShardBlockHeader( return ShardBlockHeader(
slot=block.slot, core=ShardBlockCore(
shard=block.shard, slot=block.core.slot,
beacon_chain_root=block.beacon_chain_root, beacon_chain_root=block.core.beacon_chain_root,
parent_root=block.parent_root, parent_root=block.core.parent_root,
body_root=hash_tree_root(block.body), data_root=hash_tree_root(block.core.data),
state_root=block.state_root, state_root=block.core.state_root,
attestations=block.attestations, total_bytes=block.core.total_bytes,
signature=block.signature, attester_bitfield=block.core.attester_bitfield
),
signatures=block.signatures
) )
``` ```
### `verify_shard_attestation_signature` ### `pad`
```python ```python
def verify_shard_attestation_signature(state: BeaconState, def pad(x: bytes, length: int) -> bytes:
attestation: ShardAttestation) -> None: assert len(x) <= length
data = attestation.data return x + b'\x00' * (length - len(x))
persistent_committee = get_persistent_committee(state, data.shard, data.slot) ```
pubkeys = []
for i, index in enumerate(persistent_committee): ### `flatten_shard_header`
if attestation.aggregation_bits[i]:
validator = state.validators[index] ```python
assert is_active_validator(validator, get_current_epoch(state)) def flatten_shard_header(header: ShardBlockHeader) -> Bytes[SHARD_HEADER_SIZE]:
pubkeys.append(validator.pubkey) """
assert bls_verify( Converts a shard block header into a flat object with the same hash tree root. Used
pubkey=bls_aggregate_pubkeys(pubkeys), in the crosslink construction.
message_hash=data.shard_block_root, """
signature=attestation.aggregate_signature, committee_size = len(header.core.attester_bitfield)
domain=get_domain(state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_slot(data.slot)) attester_bits = [header.core.attester_bitfield[i] if i < committee_size else 0 for i in range(256)]
attester_bytes = bytes([sum([attester_bits[i + j] << j for j in range(8)]) for i in range(0, 256, 8)])
return (
pad(int_to_bytes(header.core.slot, length=8), 32) +
header.core.beacon_chain_root +
header.core.parent_root +
header.core.data_root +
header.core.state_root +
pad(int_to_bytes(header.core.total_bytes, length=8), 32) +
attester_bytes +
b'\x00' * 32 +
pad(header.signatures.attestation_signature, 128) +
pad(header.signatures.proposer_signature, 128)
) )
``` ```
@ -251,32 +293,10 @@ def verify_shard_attestation_signature(state: BeaconState,
```python ```python
def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash: def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash:
def is_power_of_two(value: uint64) -> bool: header = b''.join([flatten_shard_header(get_shard_header(block)) for block in blocks])
return (value > 0) and (value & (value - 1) == 0) footer = b''.join([block.core.data for block in blocks])
MAX_SIZE = SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
def pad_to_power_of_2(values: MutableSequence[bytes]) -> Sequence[bytes]: return hash_tree_root(BytesN[MAX_SIZE](pad(header + footer, MAX_SIZE)))
while not is_power_of_two(len(values)):
values.append(b'\x00' * BYTES_PER_SHARD_BLOCK_BODY)
return values
def hash_tree_root_of_bytes(data: bytes) -> Hash:
return hash_tree_root([data[i:i + 32] for i in range(0, len(data), 32)])
def zpad(data: bytes, length: uint64) -> bytes:
return data + b'\x00' * (length - len(data))
return hash(
# TODO untested code.
# Need to either pass a typed list to hash-tree-root, or merkleize_chunks(values, pad_to=2**x)
hash_tree_root(pad_to_power_of_2([
hash_tree_root_of_bytes(
zpad(serialize(get_shard_header(block)), BYTES_PER_SHARD_BLOCK_BODY)
) for block in blocks
]))
+ hash_tree_root(pad_to_power_of_2([
hash_tree_root_of_bytes(block.body) for block in blocks
]))
)
``` ```
## Object validity ## Object validity
@ -287,12 +307,14 @@ Let:
- `beacon_blocks` be the `BeaconBlock` list such that `beacon_blocks[slot]` is the canonical `BeaconBlock` at slot `slot` - `beacon_blocks` be the `BeaconBlock` list such that `beacon_blocks[slot]` is the canonical `BeaconBlock` at slot `slot`
- `beacon_state` be the canonical `BeaconState` after processing `beacon_blocks[-1]` - `beacon_state` be the canonical `BeaconState` after processing `beacon_blocks[-1]`
- `shard` is the shard ID
- `valid_shard_blocks` be the list of valid `ShardBlock`, recursively defined - `valid_shard_blocks` be the list of valid `ShardBlock`, recursively defined
- `candidate` be a candidate `ShardBlock` for which validity is to be determined by running `is_valid_shard_block` - `candidate` be a candidate `ShardBlock` for which validity is to be determined by running `is_valid_shard_block`
```python ```python
def is_valid_shard_block(beacon_blocks: Sequence[BeaconBlock], def is_valid_shard_block(beacon_state: BeaconState,
beacon_state: BeaconState, beacon_blocks: Sequence[BeaconBlock],
shard: Shard,
valid_shard_blocks: Sequence[ShardBlock], valid_shard_blocks: Sequence[ShardBlock],
candidate: ShardBlock) -> bool: candidate: ShardBlock) -> bool:
# Check if block is already determined valid # Check if block is already determined valid
@ -301,80 +323,56 @@ def is_valid_shard_block(beacon_blocks: Sequence[BeaconBlock],
return True return True
# Check slot number # Check slot number
assert candidate.slot >= PHASE_1_FORK_SLOT assert compute_slot_of_shard_slot(candidate.core.slot) >= PHASE_1_FORK_SLOT
# Check shard number
assert candidate.shard <= SHARD_COUNT
# Check beacon block # Check beacon block
beacon_block = beacon_blocks[candidate.slot] beacon_block_slot = compute_start_slot_of_epoch(compute_epoch_of_shard_slot(candidate.core.slot))
assert candidate.beacon_block_root == signing_root(beacon_block) beacon_block = beacon_blocks[beacon_block_slot]
assert beacon_block.slot <= candidate.slot assert candidate.core.beacon_block_root == signing_root(beacon_block)
assert beacon_block.slot <= candidate.core.slot
# Check state root # Check state root
assert candidate.state_root == Hash() # [to be removed in phase 2] assert candidate.core.state_root == Hash() # [to be removed in phase 2]
# Check parent block # Check parent block
if candidate.slot == PHASE_1_FORK_SLOT: if candidate.core.parent_root != Hash():
assert candidate.parent_root == Hash()
else:
parent_block = next( parent_block = next(
(block for block in valid_shard_blocks if signing_root(block) == candidate.parent_root), (block for block in valid_shard_blocks if hash_tree_root(block.core) == candidate.core.parent_root),
None None
) )
assert parent_block is not None assert parent_block is not None
assert parent_block.shard == candidate.shard assert parent_block.core.slot < candidate.core.slot
assert parent_block.slot < candidate.slot parent_beacon_block_slot = compute_start_slot_of_epoch(compute_epoch_of_shard_slot(parent_block.core.slot))
assert signing_root(beacon_blocks[parent_block.slot]) == parent_block.beacon_chain_root assert signing_root(beacon_blocks[parent_beacon_block_slot]) == parent_block.core.beacon_chain_root
# Check attestations # Check attestations
assert len(candidate.attestations) <= MAX_SHARD_ATTESTIONS attester_committee = get_persistent_committee(beacon_state, shard, block.core.slot)
for _, attestation in enumerate(candidate.attestations): pubkeys = []
assert max(GENESIS_SHARD_SLOT, candidate.slot - SLOTS_PER_EPOCH) <= attestation.data.slot for i, index in enumerate(attester_committee):
assert attestation.data.slot <= candidate.slot - MIN_ATTESTATION_INCLUSION_DELAY if block.core.attester_bitfield[i]:
assert attestation.data.crosslink.shard == candidate.shard pubkeys.append(beacon_state.validators[index].pubkey)
verify_shard_attestation_signature(beacon_state, attestation) for i in range(len(attester_committee), MAX_PERSISTENT_COMMITTEE_SIZE * 2):
assert block.attester_bitfield[i] is False
assert bls_verify(
pubkey=bls_aggregate_pubkeys(pubkeys),
message_hash=candidate.core.parent_root,
signature=candidate.signatures.attestation_signature,
domain=get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(candidate.core.slot))
)
# Check signature # Check proposer
proposer_index = get_shard_proposer_index(beacon_state, candidate.shard, candidate.slot) proposer_index = get_shard_block_proposer_index(beacon_state, shard, candidate.core.slot)
assert proposer_index is not None assert proposer_index is not None
assert bls_verify( assert bls_verify(
pubkey=beacon_state.validators[proposer_index].pubkey, pubkey=beacon_state.validators[proposer_index].pubkey,
message_hash=signing_root(candidate), message_hash=hash_tree_root(candidate.core),
signature=candidate.signature, signature=candidate.signatures.proposer_signature,
domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_slot(candidate.slot)), domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(candidate.core.slot)),
) )
return True return True
``` ```
### Shard attestations
Let:
- `valid_shard_blocks` be the list of valid `ShardBlock`
- `beacon_state` be the canonical `BeaconState`
- `candidate` be a candidate `ShardAttestation` for which validity is to be determined by running `is_valid_shard_attestation`
```python
def is_valid_shard_attestation(valid_shard_blocks: Sequence[ShardBlock],
beacon_state: BeaconState,
candidate: ShardAttestation) -> bool:
# Check shard block
shard_block = next(
(block for block in valid_shard_blocks if signing_root(block) == candidate.data.shard_block_root),
None,
)
assert shard_block is not None
assert shard_block.slot == candidate.data.slot
assert shard_block.shard == candidate.data.shard
# Check signature
verify_shard_attestation_signature(beacon_state, candidate)
return True
```
### Beacon attestations ### Beacon attestations
Let: Let:

View File

@ -0,0 +1,47 @@
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import (
bls_sign,
only_with_bls,
)
from eth2spec.utils.ssz.ssz_impl import (
signing_root,
)
@only_with_bls()
def sign_shard_block(spec, state, block, shard, proposer_index=None):
if proposer_index is None:
proposer_index = spec.get_shard_block_proposer_index(state, shard, block.core.slot)
privkey = privkeys[proposer_index]
block.signatures.proposer_signature = bls_sign(
message_hash=signing_root(block),
privkey=privkey,
domain=spec.get_domain(
state,
spec.DOMAIN_SHARD_PROPOSER,
spec.compute_epoch_of_shard_slot(block.core.slot),
)
)
def build_empty_shard_block(spec, state, slot, shard, parent_root, signed=False):
if slot is None:
slot = state.slot
block = spec.ShardBlock(
core=spec.ExtendedShardBlockCore(
slot=slot,
beacon_chain_root=state.block_roots[state.slot % spec.SLOTS_PER_HISTORICAL_ROOT],
parent_root=parent_root,
),
signatures=spec.ShardBlockSignatures(
attestation_signature=b'\x12' * 96,
proposer_signature=b'\x25' * 96,
)
)
if signed:
sign_shard_block(spec, state, block, shard)
return block

View File

@ -0,0 +1,26 @@
from eth2spec.test.helpers.phase1.shard_block import (
build_empty_shard_block,
)
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
always_bls,
)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_is_valid_shard_block(spec, state):
block = build_empty_shard_block(
spec,
state,
slot=spec.Slot(spec.PERSISTENT_COMMITTEE_PERIOD * 100),
shard=spec.Shard(1),
parent_root=spec.Hash(),
signed=True,
)
# TODO: test `is_valid_shard_block`
yield 'blocks', (block,)