Merge branch 'dev' into phase1-tests

This commit is contained in:
Danny Ryan 2020-03-12 07:12:41 -06:00
commit 1293320675
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
24 changed files with 442 additions and 142 deletions

View File

@ -59,7 +59,7 @@ The following are the broad design goals for Ethereum 2.0:
* [Design Rationale](https://notes.ethereum.org/s/rkhCgQteN#)
* [Phase 0 Onboarding Document](https://notes.ethereum.org/s/Bkn3zpwxB)
* [Gasper paper](https://github.com/ethereum/research/blob/master/papers/ffg%2Bghost/paper.pdf)
* [Combining GHOST and Casper paper](https://arxiv.org/abs/2003.03052)
## For spec contributors
@ -67,4 +67,3 @@ The following are the broad design goals for Ethereum 2.0:
Documentation on the different components used during spec writing can be found here:
* [YAML Test Generators](tests/generators/README.md)
* [Executable Python Spec, with Py-tests](tests/core/pyspec/README.md)

View File

@ -21,6 +21,12 @@ SHUFFLE_ROUND_COUNT: 90
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384
# Jan 3, 2020
MIN_GENESIS_TIME: 1578009600
# 4
HYSTERESIS_QUOTIENT: 4
# 1 (minus 0.25)
HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5
# Fork Choice
@ -82,8 +88,8 @@ SLOTS_PER_EPOCH: 32
MIN_SEED_LOOKAHEAD: 1
# 2**2 (= 4) epochs 25.6 minutes
MAX_SEED_LOOKAHEAD: 4
# 2**10 (= 1,024) slots ~1.7 hours
SLOTS_PER_ETH1_VOTING_PERIOD: 1024
# 2**5 (= 32) epochs ~3.4 hours
EPOCHS_PER_ETH1_VOTING_PERIOD: 32
# 2**13 (= 8,192) slots ~13 hours
SLOTS_PER_HISTORICAL_ROOT: 8192
# 2**8 (= 256) epochs ~27 hours

View File

@ -20,6 +20,13 @@ SHUFFLE_ROUND_COUNT: 10
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
# Jan 3, 2020
MIN_GENESIS_TIME: 1578009600
# 4
HYSTERESIS_QUOTIENT: 4
# 1 (minus 0.25)
HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5
# Fork Choice
@ -82,7 +89,7 @@ MIN_SEED_LOOKAHEAD: 1
# 2**2 (= 4) epochs
MAX_SEED_LOOKAHEAD: 4
# [customized] higher frequency new deposits from eth1 for testing
SLOTS_PER_ETH1_VOTING_PERIOD: 16
EPOCHS_PER_ETH1_VOTING_PERIOD: 2
# [customized] smaller state
SLOTS_PER_HISTORICAL_ROOT: 64
# 2**8 (= 256) epochs

View File

@ -95,7 +95,7 @@ from dataclasses import (
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import (
View, boolean, Container, List, Vector, uint64,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils import bls
@ -106,7 +106,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, Optional
Any, Dict, Set, Sequence, NewType, Tuple, TypeVar, Callable
)
from dataclasses import (
@ -117,7 +117,7 @@ from dataclasses import (
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import (
View, boolean, Container, List, Vector, uint64, uint8, bit,
ByteList, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
ByteList, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils import bls
@ -191,7 +191,17 @@ get_total_active_balance = cache_this(
_get_beacon_committee = get_beacon_committee
get_beacon_committee = cache_this(
lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index),
_get_beacon_committee)'''
_get_beacon_committee)
_get_matching_target_attestations = get_matching_target_attestations
get_matching_target_attestations = cache_this(
lambda state, epoch: (state.hash_tree_root(), epoch),
_get_matching_target_attestations)
_get_matching_head_attestations = get_matching_head_attestations
get_matching_head_attestations = cache_this(
lambda state, epoch: (state.hash_tree_root(), epoch),
_get_matching_head_attestations)'''
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
@ -479,7 +489,7 @@ setup(
"pycryptodome==3.9.4",
"py_ecc==2.0.0",
"dataclasses==0.6",
"remerkleable==0.1.11",
"remerkleable==0.1.12",
"ruamel.yaml==0.16.5"
]
)

View File

@ -24,6 +24,7 @@
- [Containers](#containers)
- [Misc dependencies](#misc-dependencies)
- [`Fork`](#fork)
- [`ForkData`](#forkdata)
- [`Checkpoint`](#checkpoint)
- [`Validator`](#validator)
- [`AttestationData`](#attestationdata)
@ -75,6 +76,8 @@
- [`compute_epoch_at_slot`](#compute_epoch_at_slot)
- [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch)
- [`compute_activation_exit_epoch`](#compute_activation_exit_epoch)
- [`compute_fork_data_root`](#compute_fork_data_root)
- [`compute_fork_digest`](#compute_fork_digest)
- [`compute_domain`](#compute_domain)
- [`compute_signing_root`](#compute_signing_root)
- [Beacon state accessors](#beacon-state-accessors)
@ -149,7 +152,8 @@ We define the following Python custom types for type hinting and readability:
| `Root` | `Bytes32` | a Merkle root |
| `Version` | `Bytes4` | a fork version number |
| `DomainType` | `Bytes4` | a domain type |
| `Domain` | `Bytes8` | a signature domain |
| `ForkDigest` | `Bytes4` | a digest of the current fork data |
| `Domain` | `Bytes32` | a signature domain |
| `BLSPubkey` | `Bytes48` | a BLS12-381 public key |
| `BLSSignature` | `Bytes96` | a BLS12-381 signature |
@ -183,6 +187,10 @@ The following values are (non-configurable) constants used throughout the specif
| `SHUFFLE_ROUND_COUNT` | `90` |
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**14` (= 16,384) |
| `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) |
| `HYSTERESIS_QUOTIENT` | `4` |
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `1` |
| `HYSTERESIS_UPWARD_MULTIPLIER` | `5` |
- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
@ -213,7 +221,7 @@ The following values are (non-configurable) constants used throughout the specif
| `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes |
| `MAX_SEED_LOOKAHEAD` | `2**2` (= 4) | epochs | 25.6 minutes |
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes |
| `SLOTS_PER_ETH1_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~3.4 hours |
| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `2**5` (= 32) | epochs | ~3.4 hours |
| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours |
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days |
@ -281,6 +289,14 @@ class Fork(Container):
epoch: Epoch # Epoch of latest fork
```
#### `ForkData`
```python
class ForkData(Container):
current_version: Version
genesis_validators_root: Root
```
#### `Checkpoint`
```python
@ -377,6 +393,7 @@ class DepositData(Container):
```python
class BeaconBlockHeader(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body_root: Root
@ -396,7 +413,6 @@ class SigningRoot(Container):
```python
class ProposerSlashing(Container):
proposer_index: ValidatorIndex
signed_header_1: SignedBeaconBlockHeader
signed_header_2: SignedBeaconBlockHeader
```
@ -456,6 +472,7 @@ class BeaconBlockBody(Container):
```python
class BeaconBlock(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body: BeaconBlockBody
@ -469,6 +486,7 @@ class BeaconBlock(Container):
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
@ -478,7 +496,7 @@ class BeaconState(Container):
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, SLOTS_PER_ETH1_VOTING_PERIOD]
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
@ -788,16 +806,45 @@ def compute_activation_exit_epoch(epoch: Epoch) -> Epoch:
return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD)
```
#### `compute_fork_data_root`
```python
def compute_fork_data_root(current_version: Version, genesis_validators_root: Root) -> Root:
"""
Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``.
This is used primarily in signature domains to avoid collisions across forks/chains.
"""
return hash_tree_root(ForkData(
current_version=current_version,
genesis_validators_root=genesis_validators_root,
))
```
#### `compute_fork_digest`
```python
def compute_fork_digest(current_version: Version, genesis_validators_root: Root) -> ForkDigest:
"""
Return the 4-byte fork digest for the ``current_version`` and ``genesis_validators_root``.
This is a digest primarily used for domain separation on the p2p layer.
4-bytes suffices for practical separation of forks/chains.
"""
return ForkDigest(compute_fork_data_root(current_version, genesis_validators_root)[:4])
```
#### `compute_domain`
```python
def compute_domain(domain_type: DomainType, fork_version: Optional[Version]=None) -> Domain:
def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain:
"""
Return the domain for the ``domain_type`` and ``fork_version``.
"""
if fork_version is None:
fork_version = GENESIS_FORK_VERSION
return Domain(domain_type + fork_version)
if genesis_validators_root is None:
genesis_validators_root = Root() # all bytes zero by default
fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root)
return Domain(domain_type + fork_data_root[:28])
```
#### `compute_signing_root`
@ -950,6 +997,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei:
"""
Return the combined effective balance of the ``indices``. (1 Gwei minimum to avoid divisions by zero.)
Math safe up to ~10B ETH, afterwhich this overflows uint64.
"""
return Gwei(max(1, sum([state.validators[index].effective_balance for index in indices])))
```
@ -973,7 +1021,7 @@ def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -
"""
epoch = get_current_epoch(state) if epoch is None else epoch
fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
return compute_domain(domain_type, fork_version)
return compute_domain(domain_type, fork_version, state.genesis_validators_root)
```
#### `get_indexed_attestation`
@ -1118,6 +1166,9 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
validator.activation_eligibility_epoch = GENESIS_EPOCH
validator.activation_epoch = GENESIS_EPOCH
# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = hash_tree_root(state.validators)
return state
```
@ -1163,7 +1214,7 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida
```python
def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool:
proposer = state.validators[get_beacon_proposer_index(state)]
proposer = state.validators[signed_block.message.proposer_index]
signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER))
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
```
@ -1222,7 +1273,7 @@ def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequen
```python
def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]:
return [
a for a in get_matching_source_attestations(state, epoch)
a for a in get_matching_target_attestations(state, epoch)
if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot)
]
```
@ -1313,7 +1364,9 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
attesting_balance = get_total_balance(state, unslashed_attesting_indices)
for index in eligible_validator_indices:
if index in unslashed_attesting_indices:
rewards[index] += get_base_reward(state, index) * attesting_balance // total_balance
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow
reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
rewards[index] = reward_numerator // (total_balance // increment)
else:
penalties[index] += get_base_reward(state, index)
@ -1397,13 +1450,18 @@ def process_final_updates(state: BeaconState) -> None:
current_epoch = get_current_epoch(state)
next_epoch = Epoch(current_epoch + 1)
# Reset eth1 data votes
if (state.slot + 1) % SLOTS_PER_ETH1_VOTING_PERIOD == 0:
if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0:
state.eth1_data_votes = []
# Update effective balances with hysteresis
for index, validator in enumerate(state.validators):
balance = state.balances[index]
HALF_INCREMENT = EFFECTIVE_BALANCE_INCREMENT // 2
if balance < validator.effective_balance or validator.effective_balance + 3 * HALF_INCREMENT < balance:
HYSTERESIS_INCREMENT = EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT
DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
if (
balance + DOWNWARD_THRESHOLD < validator.effective_balance
or validator.effective_balance + UPWARD_THRESHOLD < balance
):
validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
# Reset slashings
state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0)
@ -1434,18 +1492,21 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
# Verify that the slots match
assert block.slot == state.slot
# Verify that proposer index is the correct index
assert block.proposer_index == get_beacon_proposer_index(state)
# Verify that the parent matches
assert block.parent_root == hash_tree_root(state.latest_block_header)
# Cache current block as the new latest block
state.latest_block_header = BeaconBlockHeader(
slot=block.slot,
proposer_index=block.proposer_index,
parent_root=block.parent_root,
state_root=Bytes32(), # Overwritten in the next process_slot call
body_root=hash_tree_root(block.body),
)
# Verify proposer is not slashed
proposer = state.validators[get_beacon_proposer_index(state)]
proposer = state.validators[block.proposer_index]
assert not proposer.slashed
```
@ -1468,7 +1529,7 @@ def process_randao(state: BeaconState, body: BeaconBlockBody) -> None:
```python
def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None:
state.eth1_data_votes.append(body.eth1_data)
if state.eth1_data_votes.count(body.eth1_data) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD:
if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH:
state.eth1_data = body.eth1_data
```
@ -1494,12 +1555,17 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
```python
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None:
header_1 = proposer_slashing.signed_header_1.message
header_2 = proposer_slashing.signed_header_2.message
# Verify header slots match
assert proposer_slashing.signed_header_1.message.slot == proposer_slashing.signed_header_2.message.slot
assert header_1.slot == header_2.slot
# Verify header proposer indices match
assert header_1.proposer_index == header_2.proposer_index
# Verify the headers are different
assert proposer_slashing.signed_header_1 != proposer_slashing.signed_header_2
assert header_1 != header_2
# Verify the proposer is slashable
proposer = state.validators[proposer_slashing.proposer_index]
proposer = state.validators[header_1.proposer_index]
assert is_slashable_validator(proposer, get_current_epoch(state))
# Verify signatures
for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2):
@ -1507,7 +1573,7 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla
signing_root = compute_signing_root(signed_header.message, domain)
assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature)
slash_validator(state, proposer_slashing.proposer_index)
slash_validator(state, header_1.proposer_index)
```
##### Attester slashings

View File

@ -42,7 +42,7 @@ This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0
## Fork choice
The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_checkpoint_store(genesis_state)` and update `store` by running:
The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_forkchoice_store(genesis_state)` and update `store` by running:
- `on_tick(time)` whenever `time > store.time` where `time` is the current Unix time
- `on_block(block)` whenever a block `block: SignedBeaconBlock` is received
@ -83,7 +83,7 @@ class Store(object):
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
best_justified_checkpoint: Checkpoint
blocks: Dict[Root, BeaconBlockHeader] = field(default_factory=dict)
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
@ -94,6 +94,10 @@ class Store(object):
The provided anchor-state will be regarded as a trusted state, to not roll back beyond.
This should be the genesis state for a full client.
*Note* With regards to fork choice, block headers are interchangeable with blocks. The spec is likely to move to headers for reduced overhead in test vectors and better encapsulation. Full implementations store blocks as part of their database and will often use full blocks when dealing with production fork choice.
_The block for `anchor_root` is incorrectly initialized to the block header, rather than the full block. This does not affect functionality but will be cleaned up in subsequent releases._
```python
def get_forkchoice_store(anchor_state: BeaconState) -> Store:
anchor_block_header = anchor_state.latest_block_header.copy()

View File

@ -55,6 +55,8 @@ It consists of four main sections:
- [Attestation subnet bitfield](#attestation-subnet-bitfield)
- [Interop](#interop-5)
- [Mainnet](#mainnet-5)
- [`eth2` field](#eth2-field)
- [General capabilities](#general-capabilities)
- [Topic advertisement](#topic-advertisement)
- [Mainnet](#mainnet-6)
- [Design decision rationale](#design-decision-rationale)
@ -88,13 +90,14 @@ It consists of four main sections:
- [Why are we sending entire objects in the pubsub and not just hashes?](#why-are-we-sending-entire-objects-in-the-pubsub-and-not-just-hashes)
- [Should clients gossip blocks if they *cannot* validate the proposer signature due to not yet being synced, not knowing the head block, etc?](#should-clients-gossip-blocks-if-they-cannot-validate-the-proposer-signature-due-to-not-yet-being-synced-not-knowing-the-head-block-etc)
- [How are we going to discover peers in a gossipsub topic?](#how-are-we-going-to-discover-peers-in-a-gossipsub-topic)
- [How should fork version be used in practice?](#how-should-fork-version-be-used-in-practice)
- [Req/Resp](#reqresp)
- [Why segregate requests into dedicated protocol IDs?](#why-segregate-requests-into-dedicated-protocol-ids)
- [Why are messages length-prefixed with a protobuf varint in the SSZ-encoding?](#why-are-messages-length-prefixed-with-a-protobuf-varint-in-the-ssz-encoding)
- [Why do we version protocol strings with ordinals instead of semver?](#why-do-we-version-protocol-strings-with-ordinals-instead-of-semver)
- [Why is it called Req/Resp and not RPC?](#why-is-it-called-reqresp-and-not-rpc)
- [Why do we allow empty responses in block requests?](#why-do-we-allow-empty-responses-in-block-requests)
- [Why does `BeaconBlocksByRange` let the server choose which chain to send blocks from?](#why-does-beaconblocksbyrange-let-the-server-choose-which-chain-to-send-blocks-from)
- [Why does `BeaconBlocksByRange` let the server choose which branch to send blocks from?](#why-does-beaconblocksbyrange-let-the-server-choose-which-branch-to-send-blocks-from)
- [What's the effect of empty slots on the sync algorithm?](#whats-the-effect-of-empty-slots-on-the-sync-algorithm)
- [Discovery](#discovery)
- [Why are we using discv5 and not libp2p Kademlia DHT?](#why-are-we-using-discv5-and-not-libp2p-kademlia-dht)
@ -216,7 +219,13 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master
### Topics and messages
Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/TopicName/TopicEncoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded.
Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings have form: `/eth2/ForkDigestValue/Name/Encoding`. This defines both the type of data being sent on the topic and how the data field of the message is encoded.
- `ForkDigestValue` - the lowercase hex-encoded (no "0x" prefix) bytes of `compute_fork_digest(current_fork_version, genesis_validators_root)` where
- `current_fork_version` is the fork version of the epoch of the message to be sent on the topic
- `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root`
- `Name` - see table below
- `Encoding` - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. See the [Encodings](#Encoding-strategies) section for further details.
Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit.
@ -229,7 +238,7 @@ where `base64` is the [URL-safe base64 alphabet](https://tools.ietf.org/html/rfc
The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic:
| Topic | Message Type |
| Name | Message Type |
|------------------------------------------------|-------------------------|
| beacon_block | SignedBeaconBlock |
| beacon_aggregate_and_proof | SignedAggregateAndProof |
@ -247,7 +256,7 @@ When processing incoming gossip, clients MAY descore or disconnect peers who fai
#### Global topics
There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `TopicName`s are:
There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `Name`s are:
- `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network
- The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot).
@ -265,7 +274,7 @@ There are two primary global topics used to propagate beacon blocks and aggregat
- The aggregator signature, `signed_aggregate_and_proof.signature`, is valid.
- The signature of `aggregate` is valid.
Additional global topics are used to propagate lower frequency validator messages. Their `TopicName`s are:
Additional global topics are used to propagate lower frequency validator messages. Their `Name`s are:
- `voluntary_exit` - This topic is used solely for propagating signed voluntary validator exits to proposers on the network. Signed voluntary exits are sent in their entirety. The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network
- The voluntary exit is the first valid voluntary exit received for the validator with index `signed_voluntary_exit.message.validator_index`.
@ -280,7 +289,7 @@ Additional global topics are used to propagate lower frequency validator message
#### Attestation subnets
Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `TopicName`s are:
Attestation subnets are used to propagate unaggregated attestations to subsections of the network. Their `Name`s are:
- `committee_index{subnet_id}_beacon_attestation` - These topics are used to propagate unaggregated attestations to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. The following validations MUST pass before forwarding the `attestation` on the subnet.
- The attestation's committee index (`attestation.data.index`) is for the correct subnet.
@ -414,14 +423,42 @@ Here, `result` represents the 1-byte response code.
The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time:
- `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Bytes32`'s.
- `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s.
- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy). MAY be supported in the interoperability testnet; MUST be supported in mainnet.
#### SSZ-encoding strategy (with or without Snappy)
The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded. If the Snappy variant is selected, we feed the serialized form to the Snappy compressor on encoding. The inverse happens on decoding.
The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded.
**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST prefix all encoded and compressed (if applicable) payloads with an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints).
If the Snappy variant is selected, we feed the serialized form of the object to the Snappy compressor on encoding. The inverse happens on decoding.
Snappy has two formats: "block" and "frames" (streaming). To support large requests and response chunks, snappy-framing is used.
Since snappy frame contents [have a maximum size of `65536` bytes](https://github.com/google/snappy/blob/master/framing_format.txt#L104)
and frame headers are just `identifier (1) + checksum (4)` bytes, the expected buffering of a single frame is acceptable.
**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST encode the length of the raw SSZ bytes, encoded as an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints).
*Writing*: By first computing and writing the SSZ byte length, the SSZ encoder can then directly write the chunk contents to the stream.
If Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame.
*Reading*: After reading the expected SSZ byte length, the SSZ decoder can directly read the contents from the stream.
If snappy is applied, it can be passed through a buffered Snappy reader to decompress frame by frame.
A reader SHOULD NOT read more than `max_encoded_len(n)` bytes after reading the SSZ length prefix `n` from the header.
- For `ssz` this is: `n`
- For `ssz_snappy` this is: `32 + n + n // 6`. This is considered the [worst-case compression result](https://github.com/google/snappy/blob/537f4ad6240e586970fe554614542e9717df7902/snappy.cc#L98) by Snappy.
A reader SHOULD consider the following cases as invalid input:
- A SSZ length prefix that, compared against the SSZ type information (vector lengths, list limits, integer sizes, etc.), is:
- Smaller than the expected minimum serialized length.
- Bigger than the expected maximum serialized length.
- Any remaining bytes, after having read the `n` SSZ bytes. An EOF is expected.
- An early EOF, before fully reading the declared length prefix worth of SSZ bytes.
In case of an invalid input, a reader MUST:
- From requests: send back an error message, response code `InvalidRequest`. The request itself is ignored.
- From responses: ignore the response, the response MUST be considered bad server behavior.
All messages that contain only a single field MUST be encoded directly as the type of that field and MUST NOT be encoded as an SSZ container.
@ -438,16 +475,18 @@ constituents individually as `response_chunk`s. For example, the
Request, Response Content:
```
(
head_fork_version: Bytes4
finalized_root: Bytes32
finalized_epoch: uint64
head_root: Bytes32
head_slot: uint64
fork_digest: ForkDigest
finalized_root: Root
finalized_epoch: Epoch
head_root: Root
head_slot: Slot
)
```
The fields are, as seen by the client at the time of sending the message:
- `head_fork_version`: The beacon_state `Fork` version.
- `fork_digest`: The node's `ForkDigest` (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where
- `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync)
- `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root`
- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block.
- `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block.
- `head_root`: The hash_tree_root root of the current head block.
@ -461,7 +500,7 @@ The response MUST consist of a single `response_chunk`.
Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions:
1. If `head_fork_version` does not match the expected fork version at the epoch of the `head_slot`, since the clients chain is on another fork. `head_fork_version` can also be used to segregate testnets.
1. If `fork_digest` does not match the node's local `fork_digest`, since the clients chain is on another fork.
2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, then Peer 1 would disconnect because it knows that their chains are irreparably disjoint.
Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon blocks from its counterparty via the `BeaconBlocksByRange` request.
@ -499,7 +538,7 @@ The response MUST consist of a single `response_chunk`.
Request Content:
```
(
start_slot: uint64
start_slot: Slot
count: uint64
step: uint64
)
@ -519,7 +558,8 @@ Requests count beacon blocks from the peer starting from `start_slot`, leading u
The request MUST be encoded as an SSZ-container.
The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
Clients MUST support requesting blocks since the start of the weak subjectivity period and up to the given `head_block_root`.
Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period and MUST support serving requests of blocks up to their own `head_block_root`.
Clients MUST respond with at least one block, if they have it and it exists in the range. Clients MAY limit the number of blocks in the response.
@ -537,7 +577,7 @@ Request Content:
```
(
[]Bytes32
[]Root
)
```
@ -604,6 +644,38 @@ Nonetheless, ENRs MUST carry a generic `eth2` key with nil value, denoting that
#### Mainnet
##### `eth2` field
ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current fork digest, next fork version, and next fork epoch to ensure connections are made with peers on the intended eth2 network.
| Key | Value |
|:-------------|:--------------------|
| `eth2` | SSZ `ENRForkID` |
Specifically, the value of the `eth2` key MUST be the following SSZ encoded object (`ENRForkID`)
```
(
fork_digest: ForkDigest
next_fork_version: Version
next_fork_epoch: Epoch
)
```
where the fields of `ENRForkID` are defined as
* `fork_digest` is `compute_fork_digest(current_fork_version, genesis_validators_root)` where
* `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync)
* `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root`
* `next_fork_version` is the fork version corresponding to the next planned hard fork at a future epoch. If no future fork is planned, set `next_fork_version = current_fork_version` to signal this fact
* `next_fork_epoch` is the epoch at which the next fork is planned and the `current_fork_version` will be updated. If no future fork is planned, set `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact
Clients SHOULD connect to peers with `fork_digest`, `next_fork_version`, and `next_fork_epoch` that match local values.
Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`.
##### General capabilities
On mainnet, ENRs MUST include a structure enumerating the capabilities offered by the peer in an efficient manner. The concrete solution is currently undefined. Proposals include using namespaced bloom filters mapping capabilities to specific protocol IDs supported under that capability.
### Topic advertisement
@ -753,9 +825,9 @@ For future extensibility with almost zero overhead now (besides the extra bytes
### How do we upgrade gossip channels (e.g. changes in encoding, compression)?
Changing gossipsub/broadcasts requires a coordinated upgrade where all clients start publishing to the new topic together, for example during a hard fork.
Changing gossipsub/broadcasts requires a coordinated upgrade where all clients start publishing to the new topic together, during a hard fork.
One can envision a two-phase deployment as well where clients start listening to the new topic in the first phase then start publishing some time later, letting the traffic naturally move over to the new topic.
When a node is preparing for upcoming tasks (e.g. validator duty lookahead) on a gossipsub topic, the node should join the topic of the future epoch in which the task is to occur in addition to listening to the topics for the current epoch.
### Why must all clients use the same gossip topic instead of one negotiated between each peer pair?
@ -823,6 +895,14 @@ In Phase 0, peers for attestation subnets will be found using the `attnets` entr
Although this method will be sufficient for early phases of Eth2, we aim to use the more appropriate discv5 topics for this and other similar tasks in the future. ENRs should ultimately not be used for this purpose. They are best suited to store identity, location, and capability information, rather than more volatile advertisements.
### How should fork version be used in practice?
Fork versions are to be manually updated (likely via incrementing) at each hard fork. This is to provide native domain separation for signatures as well as to aid in usefulness for identitying peers (via ENRs) and versioning network protocols (e.g. using fork version to naturally version gossipsub topics).
`BeaconState.genesis_validators_root` is mixed into signature and ENR fork domains (`ForkDigest`) to aid in the ease of domain separation between chains. This allows fork versions to safely be reused across chains except for the case of contentious forks using the same genesis. In these cases, extra care should be taken to isolate fork versions (e.g. flip a high order bit in all future versions of one of the chains).
A node locally stores all previous and future planned fork versions along with the each fork epoch. This allows for handling sync and processing messages starting from past forks/epochs.
## Req/Resp
### Why segregate requests into dedicated protocol IDs?
@ -845,23 +925,14 @@ Requests are segregated by protocol ID to:
We are using single-use streams where each stream is closed at the end of the message. Thus, libp2p transparently handles message delimiting in the underlying stream. libp2p streams are full-duplex, and each party is responsible for closing their write side (like in TCP). We can therefore use stream closure to mark the end of the request and response independently.
Nevertheless, messages are still length-prefixed—this is now being considered for removal.
Advantages of length-prefixing include:
* Reader can prepare a correctly sized buffer before reading message
Nevertheless, in the case of `ssz` and `ssz_snappy`, messages are still length-prefixed with the length of the underlying data:
* A basic reader can prepare a correctly sized buffer before reading the message
* A more advanced reader can stream-decode SSZ given the length of the SSZ data.
* Alignment with protocols like gRPC over HTTP/2 that prefix with length
* Sanity checking of stream closure / message length
* Sanity checking of message length, and enabling much stricter message length limiting based on SSZ type information,
to provide even more DOS protection than the global message length already does. E.g. a small `Status` message does not nearly require `MAX_CHUNK_SIZE` bytes.
Disadvantages include:
* Redundant methods of message delimiting—both stream end marker and length prefix
* Harder to stream as length must be known up-front
* Additional code path required to verify length
In some protocols, adding a length prefix serves as a form of DoS protection against very long messages, allowing the client to abort if an overlong message is about to be sent. In this protocol, we are globally limiting message sizes using `MAX_CHUNK_SIZE`, thus the length prefix does not afford any additional protection.
[Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) is an efficient technique to encode variable-length ints. Instead of reserving a fixed-size field of as many bytes as necessary to convey the maximum possible value, this field is elastic in exchange for 1-bit overhead per byte.
[Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) is an efficient technique to encode variable-length (unsigned here) ints. Instead of reserving a fixed-size field of as many bytes as necessary to convey the maximum possible value, this field is elastic in exchange for 1-bit overhead per byte.
### Why do we version protocol strings with ordinals instead of semver?
@ -902,17 +973,17 @@ Assuming option 0 with no special `null` encoding, consider a request for slots
Failing to provide blocks that nodes "should" have is reason to trust a peer less - for example, if a particular peer gossips a block, it should have access to its parent. If a request for the parent fails, it's indicative of poor peer quality since peers should validate blocks before gossiping them.
### Why does `BeaconBlocksByRange` let the server choose which chain to send blocks from?
### Why does `BeaconBlocksByRange` let the server choose which branch to send blocks from?
When connecting, the `Status` message gives an idea about the sync status of a particular peer, but this changes over time. By the time a subsequent `BeaconBlockByRange` request is processed, the information may be stale, and the responding side might have moved on to a new finalization point and pruned blocks around the previous head and finalized blocks.
To avoid this race condition, we allow the responding side to choose which chain to send to the requesting client. The requesting client then goes on to validate the blocks and incorporate them in their own database - because they follow the same rules, they should at this point arrive at the same chain.
To avoid this race condition, we allow the responding side to choose which branch to send to the requesting client. The requesting client then goes on to validate the blocks and incorporate them in their own database - because they follow the same rules, they should at this point arrive at the same canonical chain.
### What's the effect of empty slots on the sync algorithm?
When syncing one can only tell that a slot has been skipped on a particular chain by examining subsequent blocks and analyzing the graph formed by the parent root. Because the server side may choose to omit blocks in the response for any reason, clients must validate the graph and be prepared to fill in gaps.
When syncing one can only tell that a slot has been skipped on a particular branch by examining subsequent blocks and analyzing the graph formed by the parent root. Because the server side may choose to omit blocks in the response for any reason, clients must validate the graph and be prepared to fill in gaps.
For example, if a peer responds with blocks [2, 3] when asked for [2, 3, 4], clients may not assume that block 4 doesn't exist - it merely means that the responding peer did not send it (they may not have it yet or may maliciously be trying to hide it) and successive blocks will be needed to determine if there exists a block at slot 4 in this particular chain.
For example, if a peer responds with blocks [2, 3] when asked for [2, 3, 4], clients may not assume that block 4 doesn't exist - it merely means that the responding peer did not send it (they may not have it yet or may maliciously be trying to hide it) and successive blocks will be needed to determine if there exists a block at slot 4 in this particular branch.
## Discovery

View File

@ -27,6 +27,7 @@
- [Block proposal](#block-proposal)
- [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock)
- [Slot](#slot)
- [Proposer index](#proposer-index)
- [Parent root](#parent-root)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Randao reveal](#randao-reveal)
@ -129,7 +130,7 @@ To submit a deposit:
### Process deposit
Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~4 hours) plus `SLOTS_PER_ETH1_VOTING_PERIOD` slots (~3.4 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated.
Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~4 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~3.4 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated.
### Validator index
@ -183,8 +184,7 @@ def get_committee_assignment(state: BeaconState,
A validator can use the following function to see if they are supposed to propose during a slot. This function can only be run with a `state` of the slot in question. Proposer selection is only stable within the context of the current epoch.
```python
def is_proposer(state: BeaconState,
validator_index: ValidatorIndex) -> bool:
def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool:
return get_beacon_proposer_index(state) == validator_index
```
@ -224,11 +224,14 @@ Set `block.slot = slot` where `slot` is the current slot at which the validator
*Note*: There might be "skipped" slots between the `parent` and `block`. These skipped slots are processed in the state transition function without per-block processing.
##### Proposer index
Set `block.proposer_index = validator_index` where `validator_index` is the validator chosen to propose at this slot. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the block.
##### Parent root
Set `block.parent_root = hash_tree_root(parent)`.
#### Constructing the `BeaconBlockBody`
##### Randao reveal
@ -269,7 +272,7 @@ def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64:
```python
def voting_period_start_time(state: BeaconState) -> uint64:
eth1_voting_period_start_slot = Slot(state.slot - state.slot % SLOTS_PER_ETH1_VOTING_PERIOD)
eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH))
return compute_time_at_slot(state, eth1_voting_period_start_slot)
```
@ -519,6 +522,8 @@ Because Phase 0 does not have shards and thus does not have Shard Committees, th
* Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets
* Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR
*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements.
## How to avoid slashing
"Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed: [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed.

View File

@ -221,6 +221,7 @@ Note that the `body` has a new `BeaconBlockBody` definition.
```python
class BeaconBlock(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body: BeaconBlockBody
@ -244,6 +245,7 @@ Note that aside from the new additions, `Validator` and `PendingAttestation` hav
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
@ -253,7 +255,7 @@ class BeaconState(Container):
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT]
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, SLOTS_PER_ETH1_VOTING_PERIOD]
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]

View File

@ -48,8 +48,8 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
### Misc
| Name | Value | Unit |
| - | - |
| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` |
| - | - | - |
| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` | - |
| `BYTES_PER_CUSTODY_ATOM` | `48` | bytes |
## Configuration

View File

@ -20,7 +20,7 @@ Unlike the regular install, this outputs spec files to their original source loc
Alternatively, you can build a sub-set of the pyspec with the distutil command:
```bash
python setup.py pyspec --spec-version=phase0 --md-doc-paths="specs/phase0/beacon-chain.md specs/phase0/fork-choice.md" --out-dir=my_spec_dir
python setup.py pyspec --spec-fork=phase0 --md-doc-paths="specs/phase0/beacon-chain.md specs/phase0/fork-choice.md" --out-dir=my_spec_dir
```
## Py-tests

View File

@ -1 +1 @@
0.10.2.dev0
0.11.0

View File

@ -65,10 +65,22 @@ def apply_empty_block(spec, state):
def build_empty_block(spec, state, slot=None):
"""
Build empty block for ``slot``, built upon the latest block header seen by ``state``.
Slot must be greater than or equal to the current slot in ``state``.
"""
if slot is None:
slot = state.slot
if slot < state.slot:
raise Exception("build_empty_block cannot build blocks for past slots")
if slot > state.slot:
# transition forward in copied state to grab relevant data from state
state = state.copy()
spec.process_slots(state, slot)
empty_block = spec.BeaconBlock()
empty_block.slot = slot
empty_block.proposer_index = spec.get_beacon_proposer_index(state)
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
previous_block_header = state.latest_block_header.copy()
if previous_block_header.state_root == spec.Root():

View File

@ -43,4 +43,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold):
validator.activation_eligibility_epoch = spec.GENESIS_EPOCH
validator.activation_epoch = spec.GENESIS_EPOCH
# Set genesis validators root for domain separation and chain versioning
state.genesis_validators_root = spec.hash_tree_root(state.validators)
return state

View File

@ -10,6 +10,7 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
header_1 = spec.BeaconBlockHeader(
slot=slot,
proposer_index=validator_index,
parent_root=b'\x33' * 32,
state_root=b'\x44' * 32,
body_root=b'\x55' * 32,
@ -27,7 +28,6 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
signed_header_2 = spec.SignedBeaconBlockHeader(message=header_2)
return spec.ProposerSlashing(
proposer_index=validator_index,
signed_header_1=signed_header_1,
signed_header_2=signed_header_2,
)

View File

@ -47,6 +47,18 @@ def test_invalid_slot_block_header(spec, state):
yield from run_block_header_processing(spec, state, block, valid=False)
@with_all_phases
@spec_state_test
def test_invalid_proposer_index(spec, state):
block = build_empty_block_for_next_slot(spec, state)
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))
active_indices = [i for i in active_indices if i != block.proposer_index]
block.proposer_index = active_indices[0] # invalid proposer index
yield from run_block_header_processing(spec, state, block, valid=False)
@with_all_phases
@spec_state_test
def test_invalid_parent_root(spec, state):

View File

@ -22,22 +22,20 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True)
yield 'post', None
return
pre_proposer_balance = get_balance(state, proposer_slashing.proposer_index)
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
pre_proposer_balance = get_balance(state, proposer_index)
spec.process_proposer_slashing(state, proposer_slashing)
yield 'post', state
# check if slashed
slashed_validator = state.validators[proposer_slashing.proposer_index]
slashed_validator = state.validators[proposer_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
# lost whistleblower reward
assert (
get_balance(state, proposer_slashing.proposer_index) <
pre_proposer_balance
)
assert get_balance(state, proposer_index) < pre_proposer_balance
@with_all_phases
@ -77,7 +75,24 @@ def test_invalid_sig_1_and_2(spec, state):
def test_invalid_proposer_index(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# Index just too high (by 1)
proposer_slashing.proposer_index = len(state.validators)
proposer_slashing.signed_header_1.message.proposer_index = len(state.validators)
proposer_slashing.signed_header_2.message.proposer_index = len(state.validators)
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@with_all_phases
@spec_state_test
def test_invalid_different_proposer_indices(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set different index and sign
header_1 = proposer_slashing.signed_header_1.message
header_2 = proposer_slashing.signed_header_2.message
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))
active_indices = [i for i in active_indices if i != header_1.proposer_index]
header_2.proposer_index = active_indices[0]
proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[header_2.proposer_index])
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -89,9 +104,9 @@ def test_epochs_are_different(spec, state):
# set slots to be in different epochs
header_2 = proposer_slashing.signed_header_2.message
proposer_index = header_2.proposer_index
header_2.slot += spec.SLOTS_PER_EPOCH
proposer_slashing.signed_header_2 = sign_block_header(
spec, state, header_2, privkeys[proposer_slashing.proposer_index])
proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[proposer_index])
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -113,7 +128,8 @@ def test_proposer_is_not_activated(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set proposer to be not active yet
state.validators[proposer_slashing.proposer_index].activation_epoch = spec.get_current_epoch(state) + 1
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
state.validators[proposer_index].activation_epoch = spec.get_current_epoch(state) + 1
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -124,7 +140,8 @@ def test_proposer_is_slashed(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set proposer to slashed
state.validators[proposer_slashing.proposer_index].slashed = True
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
state.validators[proposer_index].slashed = True
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -138,7 +155,7 @@ def test_proposer_is_withdrawn(spec, state):
next_epoch(spec, state)
# set proposer withdrawable_epoch in past
current_epoch = spec.get_current_epoch(state)
proposer_index = proposer_slashing.proposer_index
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
state.validators[proposer_index].withdrawable_epoch = current_epoch - 1
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

View File

@ -12,7 +12,7 @@ def run_process_final_updates(spec, state):
@with_all_phases
@spec_state_test
def test_eth1_vote_no_reset(spec, state):
assert spec.SLOTS_PER_ETH1_VOTING_PERIOD > spec.SLOTS_PER_EPOCH
assert spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 1
# skip ahead to the end of the epoch
transition_to(spec, state, spec.SLOTS_PER_EPOCH - 1)
@ -31,7 +31,7 @@ def test_eth1_vote_no_reset(spec, state):
@spec_state_test
def test_eth1_vote_reset(spec, state):
# skip ahead to the end of the voting period
state.slot = spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1
state.slot = (spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH) - 1
for i in range(state.slot + 1): # add a vote for each skipped slot.
state.eth1_data_votes.append(
spec.Eth1Data(deposit_root=b'\xaa' * 32,
@ -53,19 +53,25 @@ def test_effective_balance_hysteresis(spec, state):
max = spec.MAX_EFFECTIVE_BALANCE
min = spec.EJECTION_BALANCE
inc = spec.EFFECTIVE_BALANCE_INCREMENT
half_inc = inc // 2
div = spec.HYSTERESIS_QUOTIENT
hys_inc = inc // div
down = spec.HYSTERESIS_DOWNWARD_MULTIPLIER
up = spec.HYSTERESIS_UPWARD_MULTIPLIER
cases = [
(max, max, max, "as-is"),
(max, max - 1, max - inc, "round down, step lower"),
(max, max - 1, max, "round up"),
(max, max + 1, max, "round down"),
(max, max - down * hys_inc, max, "lower balance, but not low enough"),
(max, max - down * hys_inc - 1, max - inc, "lower balance, step down"),
(max, max + (up * hys_inc) + 1, max, "already at max, as is"),
(max, max - inc, max - inc, "exactly 1 step lower"),
(max, max - inc - 1, max - (2 * inc), "just 1 over 1 step lower"),
(max, max - inc - 1, max - (2 * inc), "past 1 step lower, double step"),
(max, max - inc + 1, max - inc, "close to 1 step lower"),
(min, min + (half_inc * 3), min, "bigger balance, but not high enough"),
(min, min + (half_inc * 3) + 1, min + inc, "bigger balance, high enough, but small step"),
(min, min + (half_inc * 4) - 1, min + inc, "bigger balance, high enough, close to double step"),
(min, min + (half_inc * 4), min + (2 * inc), "exact two step balance increment"),
(min, min + (half_inc * 4) + 1, min + (2 * inc), "over two steps, round down"),
(min, min + (hys_inc * up), min, "bigger balance, but not high enough"),
(min, min + (hys_inc * up) + 1, min + inc, "bigger balance, high enough, but small step"),
(min, min + (hys_inc * div * 2) - 1, min + inc, "bigger balance, high enough, close to double step"),
(min, min + (hys_inc * div * 2), min + (2 * inc), "exact two step balance increment"),
(min, min + (hys_inc * div * 2) + 1, min + (2 * inc), "over two steps, round down"),
]
current_epoch = spec.get_current_epoch(state)
for i, (pre_eff, bal, _, _) in enumerate(cases):

View File

@ -2,8 +2,8 @@ from copy import deepcopy
from eth2spec.test.context import (
spec_state_test, spec_test,
with_all_phases, with_phases,
misc_balances, with_custom_state, default_activation_threshold, single_phase
with_all_phases, with_phases, single_phase,
misc_balances, with_custom_state, default_activation_threshold
)
from eth2spec.test.helpers.state import (
next_epoch,
@ -104,6 +104,29 @@ def test_full_attestations(spec, state):
assert state.balances[index] < pre_state.balances[index]
@with_all_phases
@spec_state_test
def test_full_attestations_random_incorrect_fields(spec, state):
attestations = prepare_state_with_full_attestations(spec, state)
for i, attestation in enumerate(state.previous_epoch_attestations):
if i % 3 == 0:
# Mess up some head votes
attestation.data.beacon_block_root = b'\x56' * 32
if i % 3 == 1:
# Message up some target votes
attestation.data.target.root = b'\x23' * 32
if i % 3 == 2:
# Keep some votes 100% correct
pass
yield from run_process_rewards_and_penalties(spec, state)
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
assert len(attesting_indices) > 0
# No balance checks, non-trivial base on group rewards
# Mainly for consensus tests
@with_all_phases
@spec_test
@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold)

View File

@ -119,6 +119,49 @@ def test_invalid_block_sig(spec, state):
yield 'post', None
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_proposer_index_sig_from_expected_proposer(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
expect_proposer_index = block.proposer_index
# Set invalid proposer index but correct signature wrt expected proposer
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))
active_indices = [i for i in active_indices if i != block.proposer_index]
block.proposer_index = active_indices[0] # invalid proposer index
invalid_signed_block = sign_block(spec, state, block, expect_proposer_index)
expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
yield 'blocks', [invalid_signed_block]
yield 'post', None
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_proposer_index_sig_from_proposer_index(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
# Set invalid proposer index but correct signature wrt proposer_index
active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state))
active_indices = [i for i in active_indices if i != block.proposer_index]
block.proposer_index = active_indices[0] # invalid proposer index
invalid_signed_block = sign_block(spec, state, block, block.proposer_index)
expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block))
yield 'blocks', [invalid_signed_block]
yield 'post', None
@with_all_phases
@spec_state_test
def test_skipped_slots(spec, state):
@ -187,7 +230,7 @@ def test_proposer_slashing(spec, state):
# copy for later balance lookups.
pre_state = deepcopy(state)
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
validator_index = proposer_slashing.proposer_index
validator_index = proposer_slashing.signed_header_1.message.proposer_index
assert not state.validators[validator_index].slashed
@ -489,10 +532,12 @@ def test_historical_batch(spec, state):
@spec_state_test
def test_eth1_data_votes_consensus(spec, state):
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
if spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 2:
return
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH
offset_block = build_empty_block(spec, state, slot=voting_period_slots - 1)
state_transition_and_sign_block(spec, state, offset_block)
yield 'pre', state
@ -502,14 +547,14 @@ def test_eth1_data_votes_consensus(spec, state):
blocks = []
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
for i in range(0, voting_period_slots):
block = build_empty_block_for_next_slot(spec, state)
# wait for over 50% for A, then start voting B
block.body.eth1_data.block_hash = b if i * 2 > spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
block.body.eth1_data.block_hash = b if i * 2 > voting_period_slots else a
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(signed_block)
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
assert len(state.eth1_data_votes) == voting_period_slots
assert state.eth1_data.block_hash == a
# transition to next eth1 voting period
@ -522,7 +567,7 @@ def test_eth1_data_votes_consensus(spec, state):
yield 'post', state
assert state.eth1_data.block_hash == a
assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
assert state.slot % voting_period_slots == 0
assert len(state.eth1_data_votes) == 1
assert state.eth1_data_votes[0].block_hash == c
@ -531,12 +576,14 @@ def test_eth1_data_votes_consensus(spec, state):
@spec_state_test
def test_eth1_data_votes_no_consensus(spec, state):
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
if spec.EPOCHS_PER_ETH1_VOTING_PERIOD > 2:
return
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH
pre_eth1_hash = state.eth1_data.block_hash
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
offset_block = build_empty_block(spec, state, slot=voting_period_slots - 1)
state_transition_and_sign_block(spec, state, offset_block)
yield 'pre', state
@ -545,14 +592,14 @@ def test_eth1_data_votes_no_consensus(spec, state):
blocks = []
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
for i in range(0, voting_period_slots):
block = build_empty_block_for_next_slot(spec, state)
# wait for precisely 50% for A, then start voting B for other 50%
block.body.eth1_data.block_hash = b if i * 2 >= spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
block.body.eth1_data.block_hash = b if i * 2 >= voting_period_slots else a
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(signed_block)
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
assert len(state.eth1_data_votes) == voting_period_slots
assert state.eth1_data.block_hash == pre_eth1_hash
yield 'blocks', blocks

View File

@ -1,19 +1,19 @@
from ssz_test_case import invalid_test_case, valid_test_case
from eth2spec.utils.ssz.ssz_typing import boolean, uint8, uint16, uint32, uint64, uint128, uint256, Vector, BasicType
from eth2spec.utils.ssz.ssz_typing import boolean, uint8, uint16, uint32, uint64, uint128, uint256, Vector, BasicView
from eth2spec.utils.ssz.ssz_impl import serialize
from random import Random
from typing import Dict
from typing import Dict, Type
from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object
def basic_vector_case_fn(rng: Random, mode: RandomizationMode, elem_type: BasicType, length: int):
def basic_vector_case_fn(rng: Random, mode: RandomizationMode, elem_type: Type[BasicView], length: int):
return get_random_ssz_object(rng, Vector[elem_type, length],
max_bytes_length=length * 8,
max_list_length=length,
mode=mode, chaos=False)
BASIC_TYPES: Dict[str, BasicType] = {
BASIC_TYPES: Dict[str, Type[BasicView]] = {
'bool': boolean,
'uint8': uint8,
'uint16': uint16,
@ -49,8 +49,13 @@ def invalid_cases():
for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]:
yield f'vec_{name}_{length}_nil', invalid_test_case(lambda: b'')
for mode in random_modes:
yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \
invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length - 1)))
if length == 1:
# empty bytes, no elements. It may seem valid, but empty fixed-size elements are not valid SSZ.
yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \
invalid_test_case(lambda: b"")
else:
yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \
invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length - 1)))
yield f'vec_{name}_{length}_{mode.to_name()}_one_more', \
invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length + 1)))
yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_less', \

View File

@ -1,9 +1,9 @@
from ssz_test_case import invalid_test_case, valid_test_case
from eth2spec.utils.ssz.ssz_typing import SSZType, Container, byte, uint8, uint16, \
from eth2spec.utils.ssz.ssz_typing import View, Container, byte, uint8, uint16, \
uint32, uint64, List, ByteList, Vector, Bitvector, Bitlist
from eth2spec.utils.ssz.ssz_impl import serialize
from random import Random
from typing import Dict, Tuple, Sequence, Callable
from typing import Dict, Tuple, Sequence, Callable, Type
from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object
@ -46,14 +46,14 @@ class BitsStruct(Container):
E: Bitvector[8]
def container_case_fn(rng: Random, mode: RandomizationMode, typ: SSZType):
def container_case_fn(rng: Random, mode: RandomizationMode, typ: Type[View]):
return get_random_ssz_object(rng, typ,
max_bytes_length=2000,
max_list_length=2000,
mode=mode, chaos=False)
PRESET_CONTAINERS: Dict[str, Tuple[SSZType, Sequence[int]]] = {
PRESET_CONTAINERS: Dict[str, Tuple[Type[View], Sequence[int]]] = {
'SingleFieldTestStruct': (SingleFieldTestStruct, []),
'SmallTestStruct': (SmallTestStruct, []),
'FixedTestStruct': (FixedTestStruct, []),

View File

@ -1,10 +1,10 @@
from eth2spec.utils.ssz.ssz_impl import serialize, hash_tree_root
from eth2spec.debug.encode import encode
from eth2spec.utils.ssz.ssz_typing import SSZValue, Container
from eth2spec.utils.ssz.ssz_typing import View
from typing import Callable
def valid_test_case(value_fn: Callable[[], SSZValue]):
def valid_test_case(value_fn: Callable[[], View]):
def case_fn():
value = value_fn()
yield "value", "data", encode(value)

View File

@ -1,12 +1,13 @@
from ssz_test_case import invalid_test_case, valid_test_case
from eth2spec.utils.ssz.ssz_typing import BasicType, uint8, uint16, uint32, uint64, uint128, uint256
from eth2spec.utils.ssz.ssz_typing import BasicView, uint8, uint16, uint32, uint64, uint128, uint256
from random import Random
from typing import Type
from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object
def uint_case_fn(rng: Random, mode: RandomizationMode, typ: BasicType):
def uint_case_fn(rng: Random, mode: RandomizationMode, typ: Type[BasicView]):
return get_random_ssz_object(rng, typ,
max_bytes_length=typ.byte_len,
max_bytes_length=typ.type_byte_length(),
max_list_length=1,
mode=mode, chaos=False)
@ -17,21 +18,25 @@ UINT_TYPES = [uint8, uint16, uint32, uint64, uint128, uint256]
def valid_cases():
rng = Random(1234)
for uint_type in UINT_TYPES:
yield f'uint_{uint_type.byte_len * 8}_last_byte_empty', \
valid_test_case(lambda: uint_type((2 ** ((uint_type.byte_len - 1) * 8)) - 1))
byte_len = uint_type.type_byte_length()
yield f'uint_{byte_len * 8}_last_byte_empty', \
valid_test_case(lambda: uint_type((2 ** ((byte_len - 1) * 8)) - 1))
for variation in range(5):
for mode in [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]:
yield f'uint_{uint_type.byte_len * 8}_{mode.to_name()}_{variation}', \
yield f'uint_{byte_len * 8}_{mode.to_name()}_{variation}', \
valid_test_case(lambda: uint_case_fn(rng, mode, uint_type))
def invalid_cases():
for uint_type in UINT_TYPES:
yield f'uint_{uint_type.byte_len * 8}_one_too_high', \
invalid_test_case(lambda: (2 ** (uint_type.byte_len * 8)).to_bytes(uint_type.byte_len + 1, 'little'))
byte_len = uint_type.type_byte_length()
yield f'uint_{byte_len * 8}_one_too_high', \
invalid_test_case(lambda: (2 ** (byte_len * 8)).to_bytes(byte_len + 1, 'little'))
for uint_type in [uint8, uint16, uint32, uint64, uint128, uint256]:
yield f'uint_{uint_type.byte_len * 8}_one_byte_longer', \
invalid_test_case(lambda: (2 ** (uint_type.byte_len * 8) - 1).to_bytes(uint_type.byte_len + 1, 'little'))
byte_len = uint_type.type_byte_length()
yield f'uint_{byte_len * 8}_one_byte_longer', \
invalid_test_case(lambda: (2 ** (byte_len * 8) - 1).to_bytes(byte_len + 1, 'little'))
for uint_type in [uint8, uint16, uint32, uint64, uint128, uint256]:
yield f'uint_{uint_type.byte_len * 8}_one_byte_shorter', \
invalid_test_case(lambda: (2 ** ((uint_type.byte_len - 1) * 8) - 1).to_bytes(uint_type.byte_len - 1, 'little'))
byte_len = uint_type.type_byte_length()
yield f'uint_{byte_len * 8}_one_byte_shorter', \
invalid_test_case(lambda: (2 ** ((byte_len - 1) * 8) - 1).to_bytes(byte_len - 1, 'little'))