Merge branch 'dev' into phase1-tests
This commit is contained in:
commit
1293320675
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
20
setup.py
20
setup.py
|
@ -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"
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 client’s 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 client’s chain is on another fork.
|
||||
2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, then Peer 1 would disconnect because it knows that their chains are irreparably disjoint.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.10.2.dev0
|
||||
0.11.0
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', \
|
||||
|
|
|
@ -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, []),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'))
|
||||
|
|
Loading…
Reference in New Issue