Merge pull request #2254 from ethereum/phase1-rebased

Modularize Phase1 into post-Merge features: Sharding, Custody Game, Data Availability Sampling
This commit is contained in:
Danny Ryan 2021-03-30 07:24:26 -06:00 committed by GitHub
commit 51db49988a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 1979 additions and 3844 deletions

1
.gitignore vendored
View File

@ -16,7 +16,6 @@ eth2.0-spec-tests/
# Dynamically built from Markdown spec
tests/core/pyspec/eth2spec/phase0/
tests/core/pyspec/eth2spec/phase1/
tests/core/pyspec/eth2spec/altair/
# coverage reports

View File

@ -20,7 +20,11 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER
# To check generator matching:
#$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}])
MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/phase1/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) $(wildcard $(SPEC_DIR)/networking/*.md) $(wildcard $(SPEC_DIR)/validator/*.md)
MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) \
$(wildcard $(SPEC_DIR)/merge/*.md) \
$(wildcard $(SPEC_DIR)/custody/*.md) \
$(wildcard $(SPEC_DIR)/das/*.md) \
$(wildcard $(SPEC_DIR)/sharding/*.md)
COV_HTML_OUT=.htmlcov
COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html
@ -50,7 +54,6 @@ partial_clean:
rm -rf $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/phase0
rm -rf $(PY_SPEC_DIR)/phase1
rm -rf $(PY_SPEC_DIR)/altair
rm -rf $(PY_SPEC_DIR)/$(COV_HTML_OUT)
rm -rf $(PY_SPEC_DIR)/.coverage
@ -88,11 +91,11 @@ install_test:
test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov=eth2spec.altair.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.altair.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
find_test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov=eth2spec.altair.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.altair.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
citest: pyspec
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
@ -112,10 +115,11 @@ check_toc: $(MARKDOWN_FILES:=.toc)
codespell:
codespell . --skip ./.git -I .codespell-whitelist
# TODO: add future merge, sharding, etc. packages to linting.
lint: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.phase1 -p eth2spec.altair
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair
lint_generators: pyspec
. venv/bin/activate; cd $(TEST_GENERATORS_DIR); \

View File

@ -11,7 +11,10 @@ This repository hosts the current Eth2 specifications. Discussions about design
[![GitHub release](https://img.shields.io/github/v/release/ethereum/eth2.0-specs)](https://github.com/ethereum/eth2.0-specs/releases/) [![PyPI version](https://badge.fury.io/py/eth2spec.svg)](https://badge.fury.io/py/eth2spec)
Core specifications for Eth2 clients be found in [specs](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are:
Core specifications for Eth2 clients be found in [specs](specs/). These are divided into features.
Features are researched and developed in parallel, and then consolidated into sequential upgrades when ready.
The current features are:
### Phase 0
@ -27,13 +30,35 @@ Core specifications for Eth2 clients be found in [specs](specs/). These are divi
* [Altair fork](specs/altair/fork.md)
* [Light client sync protocol](specs/altair/sync-protocol.md)
### Sharding
The sharding spec is still actively in R&D; see the most recent available pull request [here](https://github.com/ethereum/eth2.0-specs/pull/2146) and some technical details [here](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/B1YJPGkpD).
### Merge
The merge is still actively in R&D; see an [ethresear.ch](https://ethresear.ch) post describing the proposed basic mechanism [here](https://ethresear.ch/t/the-eth1-eth2-transition/6265) and the section of [ethereum.org](https://ethereum.org) describing the merge at a high level [here](https://ethereum.org/en/eth2/docking/).
The merge is still actively in R&D. The specifications outline a general direction for engineering work,
while the details are in review and may change.
* Background material:
* An [ethresear.ch](https://ethresear.ch) post [describing the basic mechanism](https://ethresear.ch/t/the-eth1-eth2-transition/6265)
* [ethereum.org](https://ethereum.org) high-level description of the merge [here](https://ethereum.org/en/eth2/docking/)
* Specifications:
* [Beacon Chain changes](specs/merge/beacon-chain.md)
* [Fork Choice changes](specs/merge/fork-choice.md)
* [Validator additions](specs/merge/validator.md)
### Sharding
Sharding follows the merge, and is divided into three parts:
* Sharding base functionality - In early engineering phase
* [Beacon Chain changes](specs/sharding/beacon-chain.md)
* [P2P Network changes](specs/sharding/p2p-interface.md)
* Custody Game - Ready, dependent on sharding
* [Beacon Chain changes](specs/custody_game/beacon-chain.md)
* [Validator custody work](specs/custody_game/validator.md)
* Data Availability Sampling - In active R&D
* Technical details [here](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/B1YJPGkpD).
* [Core types and functions](specs/das/das-core.md)
* [P2P Networking](specs/das/p2p-interface.md)
* [Fork Choice](specs/das/fork-choice.md)
* [Sampling process](specs/das/sampling.md)
### Accompanying documents can be found in [specs](specs) and include:

View File

@ -3,7 +3,7 @@
This directory contains a set of constants presets used for testing, testnets, and mainnet.
A preset file contains all the constants known for its target.
Later-fork constants can be ignored, e.g. ignore Phase 1 constants as a client that only supports Phase 0 currently.
Later-fork constants can be ignored, e.g. ignore Sharding constants as a client that only supports Phase 0 currently.
## Forking

View File

@ -0,0 +1,48 @@
# Mainnet preset - Custody Game
# Time parameters
# ---------------------------------------------------------------
# 2**1 (= 2) epochs, 12.8 minutes
RANDAO_PENALTY_EPOCHS: 2
# 2**15 (= 32,768) epochs, ~146 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768
# 2**14 (= 16,384) epochs ~73 days
EPOCHS_PER_CUSTODY_PERIOD: 16384
# 2**11 (= 2,048) epochs, ~9 days
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**15 (= 32,768) epochs, ~146 days
MAX_CHUNK_CHALLENGE_DELAY: 32768
# Misc parameters
# ---------------------------------------------------------------
# 2**256 - 189
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
# 3
CUSTODY_SECRETS: 3
# 1/1024 chance of custody bit 1
CUSTODY_PROBABILITY_EXPONENT: 10
# Max operations
# ---------------------------------------------------------------
# 2**8 (= 256)
MAX_CUSTODY_KEY_REVEALS: 256
# 2**0 (= 1)
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
# 2**2 (= 2)
MAX_CUSTODY_CHUNK_CHALLENGES: 4
# 2** 4 (= 16)
MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 16
# 2**0 (= 1)
MAX_CUSTODY_SLASHINGS: 1
# Reward and penalty quotients
# ---------------------------------------------------------------
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2
# 2**8 (= 256)
MINOR_REWARD_QUOTIENT: 256
# Signature domains
# ---------------------------------------------------------------
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000

View File

@ -0,0 +1,7 @@
# Mainnet preset - The Merge
# Fork
# ---------------------------------------------------------------
MERGE_FORK_VERSION: 0x02000000
# TBD, temporarily max uint64 value: 2**64 - 1
MERGE_FORK_SLOT: 18446744073709551615

View File

@ -1,101 +0,0 @@
# Mainnet preset - phase 1
CONFIG_NAME: "mainnet"
# phase1-fork
# ---------------------------------------------------------------
PHASE_1_FORK_VERSION: 0x01000000
# [STUB]
PHASE_1_FORK_SLOT: 0
INITIAL_ACTIVE_SHARDS: 64
# beacon-chain
# ---------------------------------------------------------------
# Misc
# 2**10 (= 1,024)
MAX_SHARDS: 1024
# 2**7 (= 128)
LIGHT_CLIENT_COMMITTEE_SIZE: 128
# 2**3 (= 8)
GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Shard block configs
# 2**20 (= 1048,576) bytes
MAX_SHARD_BLOCK_SIZE: 1048576
# 2**18 (= 262,144) bytes
TARGET_SHARD_BLOCK_SIZE: 262144
# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length.
SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
# len(SHARD_BLOCK_OFFSETS)
MAX_SHARD_BLOCKS_PER_ATTESTATION: 12
# 2**12 (= 4,096)
BYTES_PER_CUSTODY_CHUNK: 4096
# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)
CUSTODY_RESPONSE_DEPTH: 8
# Gwei values
# 2**14 (= 16,384) Gwei
MAX_GASPRICE: 16384
# 2**3 (= 8) Gwei
MIN_GASPRICE: 8
# Time parameters
# 2**3 (= 8) | online epochs
ONLINE_PERIOD: 8
# 2**8 (= 256) | epochs
LIGHT_CLIENT_COMMITTEE_PERIOD: 256
# Max operations per block
# 2**20 (= 1,048,576)
MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576
# Domain types
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
DOMAIN_LIGHT_CLIENT: 0x82000000
# custody-game spec
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
# custody-game
# ---------------------------------------------------------------
# Time parameters
# 2**1 (= 2) epochs, 12.8 minutes
RANDAO_PENALTY_EPOCHS: 2
# 2**15 (= 32,768) epochs, ~146 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768
# 2**14 (= 16,384) epochs ~73 days
EPOCHS_PER_CUSTODY_PERIOD: 16384
# 2**11 (= 2,048) epochs, ~9 days
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**15 (= 32,768) epochs, ~146 days
MAX_CHUNK_CHALLENGE_DELAY: 32768
# Misc parameters
# 2**256 - 189
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
# 3
CUSTODY_SECRETS: 3
# 2**5 (= 32) bytes
BYTES_PER_CUSTODY_ATOM: 32
# 1/1024 chance of custody bit 1
CUSTODY_PROBABILITY_EXPONENT: 10
# Max operations
# 2**8 (= 256)
MAX_CUSTODY_KEY_REVEALS: 256
# 2**0 (= 1)
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
# 2**2 (= 2)
MAX_CUSTODY_CHUNK_CHALLENGES: 4
# 2** 4 (= 16)
MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 16
# 2**0 (= 1)
MAX_CUSTODY_SLASHINGS: 1
# Reward and penalty quotients
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2
# 2**8 (= 256)
MINOR_REWARD_QUOTIENT: 256

View File

@ -0,0 +1,43 @@
# Mainnet preset - Sharding
# Fork
# ---------------------------------------------------------------
SHARDING_FORK_VERSION: 0x03000000
# TBD, temporarily max uint64 value: 2**64 - 1
SHARDING_FORK_SLOT: 18446744073709551615
# Beacon-chain
# ---------------------------------------------------------------
# Misc
# 2**10 (= 1,024)
MAX_SHARDS: 1024
# 2**6 = 64
INITIAL_ACTIVE_SHARDS: 64
# 2**3 (= 8)
GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Shard block configs
# ---------------------------------------------------------------
MAX_SHARD_HEADERS_PER_SHARD: 4
# 2**11 (= 2,048)
MAX_SAMPLES_PER_BLOCK: 2048
# 2**10 (= 1,1024)
TARGET_SAMPLES_PER_BLOCK: 1024
# Gwei values
# ---------------------------------------------------------------
# 2**33 (= 8,589,934,592) Gwei
MAX_GASPRICE: 8589934592
# 2**3 (= 8) Gwei
MIN_GASPRICE: 8
# Time parameters
# ---------------------------------------------------------------
# 2**8 (= 256) | epochs
SHARD_COMMITTEE_PERIOD: 256
# Signature domains
# ---------------------------------------------------------------
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000

View File

@ -0,0 +1,48 @@
# Minimal preset - Custody Game
# Time parameters
# ---------------------------------------------------------------
# 2**1 (= 2) epochs, 12.8 minutes
RANDAO_PENALTY_EPOCHS: 2
# [customized] quicker for testing
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 64
# [customized] quicker for testing
EPOCHS_PER_CUSTODY_PERIOD: 32
# [customized] quicker for testing
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
# [customize for faster testing]
MAX_CHUNK_CHALLENGE_DELAY: 64
# Misc parameters
# ---------------------------------------------------------------
# 2**256 - 189
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
# 3
CUSTODY_SECRETS: 3
# 1/4 chance of custody bit 1 [customized for faster testing]
CUSTODY_PROBABILITY_EXPONENT: 2
# Max operations
# ---------------------------------------------------------------
# 2**8 (= 256)
MAX_CUSTODY_KEY_REVEALS: 256
# 2**0 (= 1)
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
# [customized]
MAX_CUSTODY_CHUNK_CHALLENGES: 2
# [customized]
MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 8
# 2**0 (= 1)
MAX_CUSTODY_SLASHINGS: 1
# Reward and penalty quotients
# ---------------------------------------------------------------
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2
# 2**8 (= 256)
MINOR_REWARD_QUOTIENT: 256
# Signature domains
# ---------------------------------------------------------------
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000

View File

@ -0,0 +1,7 @@
# Minimal preset - The Merge
# Fork
# ---------------------------------------------------------------
MERGE_FORK_VERSION: 0x02000001
# TBD, temporarily max uint64 value: 2**64 - 1
MERGE_FORK_SLOT: 18446744073709551615

View File

@ -1,105 +0,0 @@
# Minimal preset - phase 1
CONFIG_NAME: "minimal"
# phase1-fork
# ---------------------------------------------------------------
# [customized] for testnet distinction
PHASE_1_FORK_VERSION: 0x01000001
# [STUB]
PHASE_1_FORK_SLOT: 0
# [customized] reduced for testing
INITIAL_ACTIVE_SHARDS: 2
# beacon-chain
# ---------------------------------------------------------------
# Misc
# [customized] reduced for testing
MAX_SHARDS: 8
# 2**7 (= 128)
LIGHT_CLIENT_COMMITTEE_SIZE: 128
# 2**3 (= 8)
GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Shard block configs
# 2**20 (= 1048,576) bytes
MAX_SHARD_BLOCK_SIZE: 1048576
# 2**18 (= 262,144) bytes
TARGET_SHARD_BLOCK_SIZE: 262144
# Note: MAX_SHARD_BLOCKS_PER_ATTESTATION is derived from the list length.
SHARD_BLOCK_OFFSETS: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
# len(SHARD_BLOCK_OFFSETS)
MAX_SHARD_BLOCKS_PER_ATTESTATION: 12
# 2**12 (= 4,096)
BYTES_PER_CUSTODY_CHUNK: 4096
# ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)
CUSTODY_RESPONSE_DEPTH: 8
# Gwei values
# 2**14 (= 16,384) Gwei
MAX_GASPRICE: 16384
# 2**3 (= 8) Gwei
MIN_GASPRICE: 8
# Time parameters
# 2**3 (= 8) | online epochs
ONLINE_PERIOD: 8
# 2**8 (= 256) | epochs
LIGHT_CLIENT_COMMITTEE_PERIOD: 256
# Max operations per block
# 2**20 (= 1,048,576)
MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS: 1048576
# Domain types
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
DOMAIN_LIGHT_CLIENT: 0x82000000
# custody-game spec
DOMAIN_CUSTODY_BIT_SLASHING: 0x83000000
DOMAIN_LIGHT_SELECTION_PROOF: 0x84000000
DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
# custody-game
# ---------------------------------------------------------------
# Time parameters
# 2**1 (= 2) epochs
RANDAO_PENALTY_EPOCHS: 2
# [customized] quicker for testing
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 64
# [customized] quicker for testing
EPOCHS_PER_CUSTODY_PERIOD: 32
# [customized] quicker for testing
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
# [customize for faster testing]
MAX_CHUNK_CHALLENGE_DELAY: 64
# Misc parameters
# 2**256 - 189
CUSTODY_PRIME: 115792089237316195423570985008687907853269984665640564039457584007913129639747
# 3
CUSTODY_SECRETS: 3
# 2**5 (= 32) bytes
BYTES_PER_CUSTODY_ATOM: 32
# 1/4 chance of custody bit 1 [customized for faster testing]
CUSTODY_PROBABILITY_EXPONENT: 2
# Max operations
# 2**8 (= 256)
MAX_CUSTODY_KEY_REVEALS: 256
# 2**0 (= 1)
MAX_EARLY_DERIVED_SECRET_REVEALS: 1
# [customized]
MAX_CUSTODY_CHUNK_CHALLENGES: 2
# [customized]
MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 8
# 2**0 (= 1)
MAX_CUSTODY_SLASHINGS: 1
# Reward and penalty quotients
EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2
# 2**8 (= 256)
MINOR_REWARD_QUOTIENT: 256

View File

@ -0,0 +1,44 @@
# Minimal preset - Sharding
# Fork
# ---------------------------------------------------------------
SHARDING_FORK_VERSION: 0x03000001
# TBD, temporarily max uint64 value: 2**64 - 1
MERGE_FORK_SLOT: 18446744073709551615
# Beacon-chain
# ---------------------------------------------------------------
# Misc
# [customized] reduced for testing
MAX_SHARDS: 8
# [customized] reduced for testing
INITIAL_ACTIVE_SHARDS: 2
# 2**3 (= 8)
GASPRICE_ADJUSTMENT_COEFFICIENT: 8
# Shard block configs
# ---------------------------------------------------------------
MAX_SHARD_HEADERS_PER_SHARD: 4
# 2**11 (= 2,048)
MAX_SAMPLES_PER_BLOCK: 2048
# 2**10 (= 1,1024)
TARGET_SAMPLES_PER_BLOCK: 1024
# Gwei values
# ---------------------------------------------------------------
# 2**33 (= 8,589,934,592) Gwei
MAX_GASPRICE: 8589934592
# 2**3 (= 8) Gwei
MIN_GASPRICE: 8
# Time parameters
# ---------------------------------------------------------------
# 2**8 (= 256) | epochs
SHARD_COMMITTEE_PERIOD: 256
# Signature domains
# ---------------------------------------------------------------
DOMAIN_SHARD_PROPOSAL: 0x80000000
DOMAIN_SHARD_COMMITTEE: 0x81000000
DOMAIN_LIGHT_CLIENT: 0x82000000

View File

@ -13,7 +13,6 @@ FUNCTION_REGEX = r'^def [\w_]*'
# Definitions in context.py
PHASE0 = 'phase0'
ALTAIR = 'altair'
PHASE1 = 'phase1'
class SpecObject(NamedTuple):
@ -141,40 +140,7 @@ SSZObject = TypeVar('SSZObject', bound=View)
CONFIG_NAME = 'mainnet'
'''
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
)
from typing import List as PyList
from dataclasses import (
dataclass,
field,
)
from lru import LRU
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes
from eth2spec.utils.ssz.ssz_typing import (
View, boolean, Container, List, Vector, uint8, uint32, uint64, bit,
ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils import bls
from eth2spec.utils.hash_function import hash
# Whenever phase 1 is loaded, make sure we have the latest phase0
from importlib import reload
reload(phase0)
SSZVariableName = str
GeneralizedIndex = NewType('GeneralizedIndex', int)
SSZObject = TypeVar('SSZObject', bound=View)
CONFIG_NAME = 'mainnet'
'''
ALTAIR_IMPORTS = '''from eth2spec.phase0 import spec as phase0
from eth2spec.config.config_util import apply_constants_config
from typing import (
@ -294,14 +260,6 @@ get_attesting_indices = cache_this(
_get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)'''
PHASE1_SUNDRY_FUNCTIONS = '''
_get_start_shard = get_start_shard
get_start_shard = cache_this(
lambda state, slot: (state.validators.hash_tree_root(), slot),
_get_start_shard, lru_size=SLOTS_PER_EPOCH * 3)'''
ALTAIR_SUNDRY_FUNCTIONS = '''
def get_generalized_index(ssz_class: Any, *path: Sequence[Union[int, SSZVariableName]]) -> GeneralizedIndex:
@ -327,10 +285,6 @@ def is_altair(fork):
return fork == ALTAIR
def is_phase1(fork):
return fork == PHASE1
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str, ordered_class_objects: Dict[str, str]) -> str:
"""
Given all the objects that constitute a spec, combine them into a single pyfile.
@ -370,7 +324,6 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str, ordered_cl
# Functions to make pyspec work
+ '\n' + PHASE0_SUNDRY_FUNCTIONS
+ ('\n' + ALTAIR_SUNDRY_FUNCTIONS if is_altair(fork) else '')
+ ('\n' + PHASE1_SUNDRY_FUNCTIONS if is_phase1(fork) else '')
)
# Since some constants are hardcoded in setup.py, the following assertions verify that the hardcoded constants are
@ -461,7 +414,6 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject:
fork_imports = {
'phase0': PHASE0_IMPORTS,
'phase1': PHASE1_IMPORTS,
'altair': ALTAIR_IMPORTS,
}
@ -515,20 +467,6 @@ class PySpecCommand(Command):
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
"""
elif is_phase1(self.spec_fork):
self.md_doc_paths = """
specs/phase0/beacon-chain.md
specs/phase0/fork-choice.md
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
specs/phase1/custody-game.md
specs/phase1/beacon-chain.md
specs/phase1/shard-transition.md
specs/phase1/fork-choice.md
specs/phase1/fork.md
specs/phase1/shard-fork-choice.md
specs/phase1/validator.md
"""
elif is_altair(self.spec_fork):
self.md_doc_paths = """
specs/phase0/beacon-chain.md

View File

@ -1,9 +1,10 @@
# Ethereum 2.0 Phase 1 -- Custody Game
# Ethereum 2.0 Custody Game -- Beacon Chain
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@ -13,8 +14,13 @@
- [Configuration](#configuration)
- [Time parameters](#time-parameters)
- [Max operations per block](#max-operations-per-block)
- [Size parameters](#size-parameters)
- [Reward and penalty quotients](#reward-and-penalty-quotients)
- [Data structures](#data-structures)
- [Extended types](#extended-types)
- [`Validator`](#validator)
- [`BeaconBlockBody`](#beaconblockbody)
- [`BeaconState`](#beaconstate)
- [New Beacon Chain operations](#new-beacon-chain-operations)
- [`CustodyChunkChallenge`](#custodychunkchallenge)
- [`CustodyChunkChallengeRecord`](#custodychunkchallengerecord)
@ -33,6 +39,7 @@
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
- [`get_custody_period_for_validator`](#get_custody_period_for_validator)
- [Per-block processing](#per-block-processing)
- [Block processing](#block-processing)
- [Custody Game Operations](#custody-game-operations)
- [Chunk challenges](#chunk-challenges)
- [Custody chunk response](#custody-chunk-response)
@ -40,14 +47,18 @@
- [Early derived secret reveals](#early-derived-secret-reveals)
- [Custody Slashings](#custody-slashings)
- [Per-epoch processing](#per-epoch-processing)
- [Epoch transition](#epoch-transition)
- [Handling of reveal deadlines](#handling-of-reveal-deadlines)
- [Final updates](#final-updates)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document details the beacon chain additions and changes in Phase 1 of Ethereum 2.0 to support the shard data custody game, building upon the [Phase 0](../phase0/beacon-chain.md) specification.
This document details the beacon chain additions and changes of Ethereum 2.0 to support the shard data custody game,
building upon the [Sharding](../sharding/beacon-chain.md) specification.
## Constants
@ -83,6 +94,14 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `uint64(2**4)` (= 16) |
| `MAX_CUSTODY_SLASHINGS` | `uint64(2**0)` (= 1) |
### Size parameters
| Name | Value | Unit |
| - | - | - |
| `BYTES_PER_CUSTODY_CHUNK` | `uint64(2**12)` (= 4,096) | bytes |
| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - |
### Reward and penalty quotients
| Name | Value |
@ -92,6 +111,45 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
## Data structures
### Extended types
#### `Validator`
```python
class Validator(sharding.Validator):
# next_custody_secret_to_reveal is initialised to the custody period
# (of the particular validator) in which the validator is activated
# = get_custody_period_for_validator(...)
next_custody_secret_to_reveal: uint64
# TODO: The max_reveal_lateness doesn't really make sense anymore.
# So how do we incentivise early custody key reveals now?
all_custody_secrets_revealed_epoch: Epoch # to be initialized to FAR_FUTURE_EPOCH
```
#### `BeaconBlockBody`
```python
class BeaconBlockBody(sharding.BeaconBlockBody):
# Custody game
chunk_challenges: List[CustodyChunkChallenge, MAX_CUSTODY_CHUNK_CHALLENGES]
chunk_challenge_responses: List[CustodyChunkResponse, MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES]
custody_key_reveals: List[CustodyKeyReveal, MAX_CUSTODY_KEY_REVEALS]
early_derived_secret_reveals: List[EarlyDerivedSecretReveal, MAX_EARLY_DERIVED_SECRET_REVEALS]
custody_slashings: List[SignedCustodySlashing, MAX_CUSTODY_SLASHINGS]
```
#### `BeaconState`
```python
class BeaconState(sharding.BeaconState):
# Future derived secrets already exposed; contains the indices of the exposed validator
# at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH],
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS]
custody_chunk_challenge_index: uint64
```
### New Beacon Chain operations
#### `CustodyChunkChallenge`
@ -293,6 +351,18 @@ def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epo
## Per-block processing
### Block processing
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_light_client_aggregate(state, block.body)
process_operations(state, block.body)
process_custody_game_operations(state, block.body)
```
### Custody Game Operations
```python
@ -550,6 +620,41 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
## Per-epoch processing
### Epoch transition
This epoch transition overrides the phase0 epoch transition:
```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
# Proof of custody
process_reveal_deadlines(state)
process_challenge_deadlines(state)
process_slashings(state)
# Sharding
process_pending_headers(state)
charge_confirmed_header_fees(state)
reset_pending_headers(state)
# Final updates
# Phase 0
process_eth1_data_reset(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_roots_update(state)
process_participation_record_updates(state)
# Proof of custody
process_custody_final_updates(state)
process_shard_epoch_increment(state)
```
### Handling of reveal deadlines
```python

View File

@ -0,0 +1,85 @@
# Ethereum 2.0 Custody Game -- Honest Validator
**Notice**: This document is a work-in-progress for researchers and implementers.
This is an accompanying document to the [Ethereum 2.0 Custody Game](./), which describes the expected actions of a "validator"
participating in the Ethereum 2.0 Custody Game.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Becoming a validator](#becoming-a-validator)
- [Beacon chain validator assignments](#beacon-chain-validator-assignments)
- [Custody slashings](#custody-slashings)
- [Custody key reveals](#custody-key-reveals)
- [Early derived secret reveals](#early-derived-secret-reveals)
- [Construct attestation](#construct-attestation)
- [How to avoid slashing](#how-to-avoid-slashing)
- [Custody slashing](#custody-slashing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
## Prerequisites
This document is an extension of the [Sharding -- Validator](../sharding/validator.md). All behaviors and definitions defined in the Sharding doc carry over unless explicitly noted or overridden.
All terminology, constants, functions, and protocol mechanics defined in the [Custody Game -- The Beacon Chain](./beacon-chain.md)
docs are requisite for this document and used throughout. Please see the Custody Game docs before continuing and use them as a reference throughout.
## Becoming a validator
Becoming a validator in Custody Game is unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#becoming-a-validator) for details.
## Beacon chain validator assignments
Beacon chain validator assignments to beacon committees and beacon block proposal are unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#validator-assignments) for details.
##### Custody slashings
Up to `MAX_CUSTODY_SLASHINGS`, [`CustodySlashing`](./beacon-chain.md#custodyslashing) objects can be included in the `block`. The custody slashings must satisfy the verification conditions found in [custody slashings processing](beacon-chain.md#custody-slashings). The validator receives a small "whistleblower" reward for each custody slashing included (THIS IS NOT CURRENTLY THE CASE BUT PROBABLY SHOULD BE).
##### Custody key reveals
Up to `MAX_CUSTODY_KEY_REVEALS`, [`CustodyKeyReveal`](./beacon-chain.md#custodykeyreveal) objects can be included in the `block`. The custody key reveals must satisfy the verification conditions found in [custody key reveal processing](beacon-chain.md#custody-key-reveals). The validator receives a small reward for each custody key reveal included.
##### Early derived secret reveals
Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./beacon-chain.md#earlyderivedsecretreveal) objects can be included in the `block`. The early derived secret reveals must satisfy the verification conditions found in [early derived secret reveal processing](beacon-chain.md#custody-key-reveals). The validator receives a small "whistleblower" reward for each early derived secrete reveal included.
#### Construct attestation
`attestation.data`, `attestation.aggregation_bits`, and `attestation.signature` are unchanged from Phase 0. But safety/validity in signing the message is premised upon calculation of the "custody bit" [TODO].
## How to avoid slashing
Proposer and Attester slashings described in Phase 0 remain in place with the addition of the following.
### Custody slashing
To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret:
```python
def get_custody_secret(state: BeaconState,
validator_index: ValidatorIndex,
privkey: int,
epoch: Epoch=None) -> BLSSignature:
if epoch is None:
epoch = get_current_epoch(state)
period = get_custody_period_for_validator(validator_index, epoch)
epoch_to_sign = get_randao_epoch_for_custody_period(period, validator_index)
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
signing_root = compute_signing_root(Epoch(epoch_to_sign), domain)
return bls.Sign(privkey, signing_root)
```
Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated.
While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing.

190
specs/das/das-core.md Normal file
View File

@ -0,0 +1,190 @@
# Ethereum 2.0 Data Availability Sampling -- Core
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Custom types](#custom-types)
- [Configuration](#configuration)
- [Misc](#misc)
- [New containers](#new-containers)
- [DASSample](#dassample)
- [Helper functions](#helper-functions)
- [Reverse bit ordering](#reverse-bit-ordering)
- [`reverse_bit_order`](#reverse_bit_order)
- [`reverse_bit_order_list`](#reverse_bit_order_list)
- [Data extension](#data-extension)
- [Data recovery](#data-recovery)
- [DAS functions](#das-functions)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `SampleIndex` | `uint64` | A sample index, corresponding to chunk of extended data |
## Configuration
### Misc
| Name | Value | Notes |
| - | - | - |
| `MAX_RESAMPLE_TIME` | `TODO` (= TODO) | Time window to sample a shard blob and put it on vertical subnets |
## New containers
### DASSample
```python
class DASSample(Container):
slot: Slot
shard: Shard
index: SampleIndex
proof: BLSCommitment
data: Vector[BLSPoint, POINTS_PER_SAMPLE]
```
## Helper functions
### Reverse bit ordering
#### `reverse_bit_order`
```python
def reverse_bit_order(n: int, order: int):
"""
Reverse the bit order of an integer n
"""
assert is_power_of_two(order)
return int(('{:0' + str(order.bit_length() - 1) + 'b}').format(n)[::-1], 2)
```
#### `reverse_bit_order_list`
```python
def reverse_bit_order_list(elements: Sequence[int]) -> Sequence[int]:
order = len(elements)
assert is_power_of_two(order)
return [elements[reverse_bit_order(i, order)] for i in range(order)]
```
### Data extension
Implementations:
- [Python](https://github.com/protolambda/partial_fft/blob/master/das_fft.py)
- [Go](https://github.com/protolambda/go-kate/blob/master/das_extension.go)
```python
def das_fft_extension(data: Sequence[Point]) -> Sequence[Point]:
"""
Given some even-index values of an IFFT input, compute the odd-index inputs,
such that the second output half of the IFFT is all zeroes.
"""
poly = inverse_fft(data)
return fft(poly + [0]*len(poly))[1::2]
```
### Data recovery
See [Reed-Solomon erasure code recovery in n*log^2(n) time with FFTs](https://ethresear.ch/t/reed-solomon-erasure-code-recovery-in-n-log-2-n-time-with-ffts/3039) for theory.
Implementations:
- [Original Python](https://github.com/ethereum/research/blob/master/mimc_stark/recovery.py)
- [New optimized approach in python](https://github.com/ethereum/research/tree/master/polynomial_reconstruction)
- [Old approach in Go](https://github.com/protolambda/go-kate/blob/master/recovery.go)
```python
def recover_data(data: Sequence[Optional[Sequence[Point]]]) -> Sequence[Point]:
"""Given an a subset of half or more of subgroup-aligned ranges of values, recover the None values."""
...
```
## DAS functions
```python
def extend_data(data: Sequence[Point]) -> Sequence[Point]:
"""
The input data gets reverse-bit-ordered, such that the first half of the final output matches the original data.
We calculated the odd-index values with the DAS FFT extension, reverse-bit-order to put them in the second half.
"""
rev_bit_odds = reverse_bit_order_list(das_fft_extension(reverse_bit_order_list(data)))
return data + rev_bit_odds
```
```python
def unextend_data(extended_data: Sequence[Point]) -> Sequence[Point]:
return extended_data[:len(extended_data)//2]
```
```python
def check_multi_kzg_proof(commitment: BLSCommitment, proof: BLSCommitment, x: Point, ys: Sequence[Point]) -> bool:
"""
Run a KZG multi-proof check to verify that for the subgroup starting at x,
the proof indeed complements the ys to match the commitment.
"""
... # Omitted for now, refer to KZG implementation resources.
```
```python
def construct_proofs(extended_data_as_poly: Sequence[Point]) -> Sequence[BLSCommitment]:
"""
Constructs proofs for samples of extended data (in polynomial form, 2nd half being zeroes).
Use the FK20 multi-proof approach to construct proofs for a chunk length of POINTS_PER_SAMPLE.
"""
... # Omitted for now, refer to KZG implementation resources.
```
```python
def commit_to_data(data_as_poly: Sequence[Point]) -> BLSCommitment:
"""Commit to a polynomial by """
```
```python
def sample_data(slot: Slot, shard: Shard, extended_data: Sequence[Point]) -> Sequence[DASSample]:
sample_count = len(extended_data) // POINTS_PER_SAMPLE
assert sample_count <= MAX_SAMPLES_PER_BLOCK
# get polynomial form of full extended data, second half will be all zeroes.
poly = ifft(reverse_bit_order_list(extended_data))
assert all(v == 0 for v in poly[len(poly)//2:])
proofs = construct_proofs(poly)
return [
DASSample(
slot=slot,
shard=shard,
# The proof applies to `x = w ** (reverse_bit_order(i, sample_count) * POINTS_PER_SAMPLE)`
index=i,
# The computed proofs match the reverse_bit_order_list(extended_data), undo that to get the right proof.
proof=proofs[reverse_bit_order(i, sample_count)],
# note: we leave the sample data as-is so it matches the original nicely.
# The proof applies to `ys = reverse_bit_order_list(sample.data)`
data=extended_data[i*POINTS_PER_SAMPLE:(i+1)*POINTS_PER_SAMPLE]
) for i in range(sample_count)
]
```
```python
def verify_sample(sample: DASSample, sample_count: uint64, commitment: BLSCommitment):
domain_pos = reverse_bit_order(sample.index, sample_count)
sample_root_of_unity = ROOT_OF_UNITY**MAX_SAMPLES_PER_BLOCK # change point-level to sample-level domain
x = sample_root_of_unity**domain_pos
ys = reverse_bit_order_list(sample.data)
assert check_multi_kzg_proof(commitment, sample.proof, x, ys)
```
```python
def reconstruct_extended_data(samples: Sequence[Optional[DASSample]]) -> Sequence[Point]:
# Instead of recovering with a point-by-point approach, recover the samples by recovering missing subgroups.
subgroups = [None if sample is None else reverse_bit_order_list(sample.data) for sample in samples]
return recover_data(subgroups)
```

46
specs/das/fork-choice.md Normal file
View File

@ -0,0 +1,46 @@
# Ethereum 2.0 Data Availability Sampling -- Fork Choice
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Dependency calculation](#dependency-calculation)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document is the beacon chain fork choice spec for Ethereum 2.0 Data Availability Sampling. The only change that we add from phase 0 is that we add a concept of "data dependencies";
a block is only eligible for consideration in the fork choice after a data availability test has been successfully completed for all dependencies.
The "root" of a shard block for data dependency purposes is considered to be a `DataCommitment` object, which is a pair of a Kate commitment and a length.
## Dependency calculation
```python
def get_new_dependencies(state: BeaconState) -> Set[DataCommitment]:
return set(
# Already confirmed during this epoch
[c.commitment for c in state.current_epoch_pending_headers if c.confirmed] +
# Already confirmed during previous epoch
[c.commitment for c in state.previous_epoch_pending_headers if c.confirmed] +
# Confirmed in the epoch before the previous
[c for c in shard for shard in state.grandparent_epoch_confirmed_commitments if c != DataCommitment()]
)
```
```python
def get_all_dependencies(store: Store, block: BeaconBlock) -> Set[DataCommitment]:
if block.slot < SHARDING_FORK_SLOT:
return set()
else:
latest = get_new_dependencies(store.block_states[hash_tree_root(block)])
older = get_all_dependencies(store, store.blocks[block.parent_root])
return latest.union(older)
```

229
specs/das/p2p-interface.md Normal file
View File

@ -0,0 +1,229 @@
# Ethereum 2.0 Data Availability Sampling -- Network specification
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [DAS Subnets](#das-subnets)
- [Horizontal subnets](#horizontal-subnets)
- [Publishing](#publishing)
- [Horizontal propagation](#horizontal-propagation)
- [Horizontal to vertical](#horizontal-to-vertical)
- [Vertical subnets](#vertical-subnets)
- [Slow rotation: Backbone](#slow-rotation-backbone)
- [Quick Rotation: Sampling](#quick-rotation-sampling)
- [DAS in the Gossip domain: Push](#das-in-the-gossip-domain-push)
- [Topics and messages](#topics-and-messages)
- [Horizontal subnets: `shard_blob_{shard}`](#horizontal-subnets-shard_blob_shard)
- [Vertical subnets: `das_sample_{subnet_index}`](#vertical-subnets-das_sample_subnet_index)
- [DAS in the Req-Resp domain: Pull](#das-in-the-req-resp-domain-pull)
- [Messages](#messages)
- [DASQuery](#dasquery)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
For an introduction about DAS itself, see [the DAS participation spec](sampling.md#data-availability-sampling).
This is not a pre-requisite for the network layer, but will give you valuable context.
For sampling, all nodes need to query for `k` random samples each slot.
*__TODO__: describe big picture of sampling workload size*
This is a lot of work, and ideally happens at a low latency.
To achieve quick querying, the query model is changed to *push* the samples to listeners instead, using GossipSub.
The listeners then randomly rotate their subscriptions to keep queries unpredictable.
Except for a small subset of subscriptions, which will function as a backbone to keep topics more stable and allow for efficient peer discovery.
Publishing can utilize the fan-out functionality in GossipSub, and is easier to split between nodes:
nodes on the horizontal networks can help by producing the same samples and fan-out publishing to their own peers.
This push model also helps to obfuscate the original source of a message:
the listeners do not have to make individual queries to some identified source.
The push model does not aim to serve "historical" queries (anything older than the most recent).
Historical queries are still required for the unhappy case, where messages are not pushed quick enough,
and missing samples are not reconstructed by other nodes on the horizontal subnet quick enough.
The main challenge in supporting historical queries is to target the right nodes,
without concentrating too many requests on a single node, or breaking the network/consensus identity separation.
## DAS Subnets
On a high level, the push-model roles are divided into:
- Sources: create blobs of shard block data, and transformed into many tiny samples.
- Sinks: continuously look for samples
At full operation, the network has one proposer, per shard, per slot.
In the push-model, there are:
- *Vertical subnets*: Sinks can subscribe to indices of samples: there is a sample to subnet mapping.
- *Horizontal subnets*: Sources need to distribute samples to all vertical networks: they participate in a fan-out layer.
### Horizontal subnets
The shift of the distribution responsibility to a proposer can only be achieved with amplification:
a regular proposer cannot reach every vertical subnet.
#### Publishing
To publish their work, proposers propagate the shard block as a whole on a shard-block subnet.
The proposer can fan-out their work more aggressively, by using the fan-out functionality of GossipSub:
it may publish to all its peers on the subnet, instead of just those in its mesh.
#### Horizontal propagation
Peers on the horizontal subnet are expected to at least perform regular propagation of shard blocks, like participation in any other topic.
*Although this may be sufficient for testnets, expect parameter changes in the spec here.*
#### Horizontal to vertical
Nodes on this same subnet can replicate the sampling efficiently (including a proof for each sample),
and distribute it to any vertical networks that are available to them.
Since the messages are content-addressed (instead of origin-stamped),
multiple publishers of the same samples on a vertical subnet do not hurt performance,
but actually improve it by shortcutting regular propagation on the vertical subnet, and thus lowering the latency to a sample.
### Vertical subnets
Vertical subnets propagate the samples to every peer that is interested.
These interests are randomly sampled and rotate quickly: although not perfect,
sufficient to avoid any significant amount of nodes from being 100% predictable.
As soon as a sample is missing after the expected propagation time window,
nodes can divert to the pull-model, or ultimately flag it as unavailable data.
Note that the vertical subnets are shared between the different shards,
and a simple hash function `(shard, slot, sample_index) -> subnet_index` defines which samples go where.
This is to evenly distribute samples to subnets, even when one shard has more activity than the other.
TODO: define `(shard, slot, sample_index) -> subnet_index` hash function.
#### Slow rotation: Backbone
To allow for subscriptions to rotate quickly and randomly, a backbone is formed to help onboard peers into other topics.
This backbone is based on a pure function of the *node* identity and time:
- Nodes can be found *without additional discovery overhead*:
peers on a vertical topic can be found by searching the local peerstore for identities that hash to the desired topic(s),
assuming the peerstore already has a large enough variety of peers.
- Nodes can be held accountable for contributing to the backbone:
peers that particpate in DAS but are not active on the appropriate backbone topics can be scored down.
*Note: This is experimental, DAS should be light enough for all participants to run, but scoring needs to undergo testing*
A node should anticipate backbone topics to subscribe to based their own identity.
These subscriptions rotate slowly, and with different offsets per node identity to avoid sudden network-wide rotations.
```python
# TODO hash function: (node, time)->subnets
```
Backbone subscription work is outlined in the [DAS participation spec](sampling.md#slow-rotation-backbone)
#### Quick Rotation: Sampling
A node MUST maintain `k` random subscriptions to topics, and rotate these according to the [DAS participation spec](sampling.md#quick-rotation-sampling).
If the node does not already have connected peers on the topic it needs to sample, it can search its peerstore and, if necessary, in the DHT for peers in the topic backbone.
## DAS in the Gossip domain: Push
### Topics and messages
Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface.md#topics-and-messages), names and payload types are:
| Name | Message Type |
|----------------------------------|---------------------------|
| `das_sample_{subnet_index}` | `DASSample` |
Also see the [Sharding general networking spec](../sharding/p2p-interface.md) for important topics such as that of the shard-blobs and shard-headers.
#### Horizontal subnets: `shard_blob_{shard}`
Extending the regular `shard_blob_{shard}` as [defined in the Sharding networking specification](../sharding/p2p-interface.md#shard-blobs-shard_blob_shard)
If participating in DAS, upon receiving a `signed_blob` for the first time with a `slot` not older than `MAX_RESAMPLE_TIME`,
a subscriber of a `shard_blob_{shard}` SHOULD reconstruct the samples and publish them to vertical subnets.
Take `blob = signed_blob.blob`:
1. Extend the data: `extended_data = extend_data(blob.data)`
2. Create samples with proofs: `samples = sample_data(blob.slot, blob.shard, extended_data)`
3. Fanout-publish the samples to the vertical subnets of its peers (not all vertical subnets may be reached).
The [DAS participation spec](sampling.md#horizontal-subnets) outlines when and where to participate in DAS on horizontal subnets.
#### Vertical subnets: `das_sample_{subnet_index}`
Shard blob samples can be verified with just a 48 byte KZG proof (commitment quotient polynomial),
against the commitment to blob polynomial, specific to that `(shard, slot)` key.
The following validations MUST pass before forwarding the `sample` on the vertical subnet.
- _[IGNORE]_ The commitment for the (`sample.shard`, `sample.slot`, `sample.index`) tuple must be known.
If not known, the client MAY queue the sample if it passes formatting conditions.
- _[REJECT]_ `sample.shard`, `sample.slot` and `sample.index` are hashed into a `sbunet_index` (TODO: define hash) which MUST match the topic `{subnet_index}` parameter.
- _[REJECT]_ `sample.shard` must be within valid range: `0 <= sample.shard < get_active_shard_count(state, compute_epoch_at_slot(sample.slot))`.
- _[REJECT]_ `sample.index` must be within valid range: `0 <= sample.index < sample_count`, where:
- `sample_count = (points_count + POINTS_PER_SAMPLE - 1) // POINTS_PER_SAMPLE`
- `points_count` is the length as claimed along with the commitment, which must be smaller than `MAX_SAMPLES_PER_BLOCK`.
- _[IGNORE]_ The `sample` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. validate that `sample.slot <= current_slot`. A client MAY queue future samples for processing at the appropriate slot if it passed formatting conditions.
- _[IGNORE]_ This is the first received sample with the (`sample.shard`, `sample.slot`, `sample.index`) key tuple.
- _[REJECT]_ As already limited by the SSZ list-limit, it is important the sample data is well-formatted and not too large.
- _[REJECT]_ The `sample.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid.
- _[REJECT]_ The `sample.proof` MUST be valid: `verify_sample(sample, sample_count, commitment)`
Upon receiving a valid sample, it SHOULD be retained for a buffer period if the local node is part of the backbone that covers this sample.
This is to serve other peers that may have missed it.
## DAS in the Req-Resp domain: Pull
To pull samples from nodes, in case of network instability when samples are unavailable, a new query method is added to the Req-Resp domain.
This builds on top of the protocol identification and encoding spec which was introduced in [the Phase0 network spec](../phase0/p2p-interface.md).
Note that DAS networking uses a different protocol prefix: `/eth2/das/req`
The result codes are extended with:
- 3: **ResourceUnavailable** -- when the request was valid but cannot be served at this point in time.
TODO: unify with phase0? Lighthoue already defined this in their response codes enum.
### Messages
#### DASQuery
**Protocol ID:** `/eth2/das/req/query/1/`
Request Content:
```
(
sample_index: SampleIndex
)
```
Response Content:
```
(
DASSample
)
```
When the sample is:
- Available: respond with a `Success` result code, and the encoded sample.
- Expected to be available, but not: respond with a `ResourceUnavailable` result code.
- Not available, but never of interest to the node: respond with an `InvalidRequest` result code.
When the node is part of the backbone and expected to have the sample, the validity of the quest MUST be recognized with `Success` or `ResourceUnavailable`.

84
specs/das/sampling.md Normal file
View File

@ -0,0 +1,84 @@
# Ethereum 2.0 Data Availability Sampling
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Data Availability Sampling](#data-availability-sampling)
- [GossipSub](#gossipsub)
- [Horizontal subnets](#horizontal-subnets)
- [Vertical subnets](#vertical-subnets)
- [Slow rotation: Backbone](#slow-rotation-backbone)
- [Quick rotation: Sampling](#quick-rotation-sampling)
- [DAS during network instability](#das-during-network-instability)
- [Stage 0: Waiting on missing samples](#stage-0-waiting-on-missing-samples)
- [Stage 1: Pulling missing samples from known peers](#stage-1-pulling-missing-samples-from-known-peers)
- [Stage 2: Pulling missing data from validators with custody.](#stage-2-pulling-missing-data-from-validators-with-custody)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Data Availability Sampling
TODO: Summary of Data Availability problem
TODO: Summary of solution, why 2x extension, and randomized samples
## GossipSub
### Horizontal subnets
TODO
### Vertical subnets
#### Slow rotation: Backbone
TODO
#### Quick rotation: Sampling
TODO
### DAS during network instability
The GossipSub based retrieval of samples may not always work.
In such event, a node can move through below stages until it recovers data availability.
#### Stage 0: Waiting on missing samples
Wait for the sample to re-broadcast. Someone may be slow with publishing, or someone else is able to do the work.
Any node can do the following work to keep the network healthy:
- Common: Listen on a horizontal subnet, chunkify the block data in samples, and propagate the samples to vertical subnets.
- Extreme: Listen on enough vertical subnets, reconstruct the missing samples by recovery, and propagate the recovered samples.
This is not a requirement, but should improve the network stability with little resources, and without any central party.
#### Stage 1: Pulling missing samples from known peers
The more realistic option, to execute when a sample is missing, is to query any node that is known to hold it.
Since *consensus identity is disconnected from network identity*, there is no direct way to contact custody holders
without explicitly asking for the data.
However, *network identities* are still used to build a backbone for each vertical subnet.
These nodes should have received the samples, and can serve a buffer of them on demand.
Although serving these is not directly incentivised, it is little work:
1. Buffer any message you see on the backbone vertical subnets, for a buffer of up to two weeks.
2. Serve the samples on request. An individual sample is just expected to be `~ 0.5 KB`, and does not require any pre-processing to serve.
A validator SHOULD make a `DASQuery` request to random peers, until failing more than the configured failure-rate.
TODO: detailed failure-mode spec. Stop after trying e.g. 3 peers for any sample in a configured time window (after the gossip period).
#### Stage 2: Pulling missing data from validators with custody.
Pulling samples directly from nodes with validators that have a custody responsibility,
without revealing their identity to the network, is an open problem.

View File

@ -139,7 +139,7 @@
This document represents the specification for Phase 0 of Ethereum 2.0 -- The Beacon Chain.
At the core of Ethereum 2.0 is a system chain called the "beacon chain". The beacon chain stores and manages the registry of validators. In the initial deployment phases of Ethereum 2.0, the only mechanism to become a validator is to make a one-way ETH transaction to a deposit contract on Ethereum 1.0. Activation as a validator happens when Ethereum 1.0 deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior.
The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block (Phase 1) and proof-of-stake votes for a beacon block (Phase 0).
The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block (in a later Eth2 upgrade) and proof-of-stake votes for a beacon block (Phase 0).
## Notation

View File

@ -433,7 +433,7 @@ The following validations MUST pass before forwarding the `attestation` on the s
Attestation broadcasting is grouped into subnets defined by a topic.
The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`.
The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`.
`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees in Phase 1.
`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees (future Eth2 upgrade).
The subnets are rotated through with `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` subnets per slot.
Unaggregated attestations are sent as `Attestation`s to the subnet topic,

View File

@ -513,7 +513,7 @@ The `subnet_id` for the `attestation` is calculated with:
def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64:
"""
Compute the correct subnet for an attestation for Phase 0.
Note, this mimics expected Phase 1 behavior where attestations will be mapped to their shard subnet.
Note, this mimics expected future behavior where attestations will be mapped to their shard subnet.
"""
slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH)
committees_since_epoch_start = committees_per_slot * slots_since_epoch_start

File diff suppressed because it is too large Load Diff

View File

@ -1,108 +0,0 @@
# Ethereum 2.0 Phase 1 -- Beacon Chain Fork Choice
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Updated data structures](#updated-data-structures)
- [Extended `Store`](#extended-store)
- [New data structures](#new-data-structures)
- [`ShardLatestMessage`](#shardlatestmessage)
- [`ShardStore`](#shardstore)
- [Updated helpers](#updated-helpers)
- [Updated `get_forkchoice_store`](#updated-get_forkchoice_store)
- [Updated `update_latest_messages`](#updated-update_latest_messages)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.
### Updated data structures
#### Extended `Store`
```python
@dataclass
class Store(object):
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
best_justified_checkpoint: Checkpoint
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)
shard_stores: Dict[Shard, ShardStore] = field(default_factory=dict)
```
### New data structures
#### `ShardLatestMessage`
```python
@dataclass(eq=True, frozen=True)
class ShardLatestMessage(object):
epoch: Epoch
root: Root
```
#### `ShardStore`
```python
@dataclass
class ShardStore:
shard: Shard
signed_blocks: Dict[Root, SignedShardBlock] = field(default_factory=dict)
block_states: Dict[Root, ShardState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, ShardLatestMessage] = field(default_factory=dict)
```
### Updated helpers
#### Updated `get_forkchoice_store`
```python
def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store:
assert anchor_block.state_root == hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block)
anchor_epoch = get_current_epoch(anchor_state)
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
return Store(
time=anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot,
genesis_time=anchor_state.genesis_time,
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
best_justified_checkpoint=justified_checkpoint,
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: anchor_state.copy()},
checkpoint_states={justified_checkpoint: anchor_state.copy()},
shard_stores={
Shard(shard): get_forkchoice_shard_store(anchor_state, Shard(shard))
for shard in range(get_active_shard_count(anchor_state))
}
)
```
#### Updated `update_latest_messages`
```python
def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None:
target = attestation.data.target
beacon_block_root = attestation.data.beacon_block_root
# TODO: separate shard chain vote
shard = attestation.data.shard
for i in attesting_indices:
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
shard_latest_message = ShardLatestMessage(epoch=target.epoch, root=attestation.data.shard_head_root)
store.shard_stores[shard].latest_messages[i] = shard_latest_message
```

View File

@ -1,111 +0,0 @@
# Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Phase 1](#fork-to-phase-1)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
This document describes the process of moving from Phase 0 to Phase 1 of Ethereum 2.0.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
| `PHASE_1_FORK_SLOT` | `Slot(0)` **TBD** |
## Fork to Phase 1
### Fork trigger
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at slot `PHASE_1_FORK_SLOT`, where `PHASE_1_FORK_SLOT % SLOTS_PER_EPOCH == 0`.
### Upgrading the state
After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_FORK_SLOT`, an irregular state change is made to upgrade to Phase 1.
```python
def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
epoch = get_current_epoch(pre)
post = BeaconState(
genesis_time=pre.genesis_time,
slot=pre.slot,
fork=Fork(
previous_version=pre.fork.current_version,
current_version=PHASE_1_FORK_VERSION,
epoch=epoch,
),
# History
latest_block_header=pre.latest_block_header,
block_roots=pre.block_roots,
state_roots=pre.state_roots,
historical_roots=pre.historical_roots,
# Eth1
eth1_data=pre.eth1_data,
eth1_data_votes=pre.eth1_data_votes,
eth1_deposit_index=pre.eth1_deposit_index,
# Registry
validators=List[Validator, VALIDATOR_REGISTRY_LIMIT](
Validator(
pubkey=phase0_validator.pubkey,
withdrawal_credentials=phase0_validator.withdrawal_credentials,
effective_balance=phase0_validator.effective_balance,
slashed=phase0_validator.slashed,
activation_eligibility_epoch=phase0_validator.activation_eligibility_epoch,
activation_epoch=phase0_validator.activation_eligibility_epoch,
exit_epoch=phase0_validator.exit_epoch,
withdrawable_epoch=phase0_validator.withdrawable_epoch,
next_custody_secret_to_reveal=get_custody_period_for_validator(ValidatorIndex(i), epoch),
all_custody_secrets_revealed_epoch=FAR_FUTURE_EPOCH,
) for i, phase0_validator in enumerate(pre.validators)
),
balances=pre.balances,
# Randomness
randao_mixes=pre.randao_mixes,
# Slashings
slashings=pre.slashings,
# Attestations
# previous_epoch_attestations is cleared on upgrade.
previous_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](),
# empty in pre state, since the upgrade is performed just after an epoch boundary.
current_epoch_attestations=List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH](),
# Finality
justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint,
current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint,
# Phase 1
current_epoch_start_shard=Shard(0),
shard_states=List[ShardState, MAX_SHARDS](
ShardState(
slot=compute_previous_slot(pre.slot),
gasprice=MIN_GASPRICE,
latest_block_root=Root(),
) for i in range(INITIAL_ACTIVE_SHARDS)
),
online_countdown=[ONLINE_PERIOD] * len(pre.validators), # all online
current_light_committee=CompactCommittee(), # computed after state creation
next_light_committee=CompactCommittee(),
# Custody game
exposed_derived_secrets=[()] * EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS,
# exposed_derived_secrets will fully default to zeroes
)
next_epoch = Epoch(epoch + 1)
post.current_light_committee = committee_to_compact_committee(post, get_light_client_committee(post, epoch))
post.next_light_committee = committee_to_compact_committee(post, get_light_client_committee(post, next_epoch))
return post
```

View File

@ -1,174 +0,0 @@
# Minimal Light Client Design
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Custom types](#custom-types)
- [Constants](#constants)
- [Containers](#containers)
- [`LightClientUpdate`](#lightclientupdate)
- [Helpers](#helpers)
- [`LightClientMemory`](#lightclientmemory)
- [`get_persistent_committee_pubkeys_and_balances`](#get_persistent_committee_pubkeys_and_balances)
- [Light client state updates](#light-client-state-updates)
- [Data overhead](#data-overhead)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
Ethereum 2.0 is designed to be light client friendly. This allows low-resource clients such as mobile phones to access Ethereum 2.0 with reasonable safety and liveness. It also facilitates the development of "bridges" to external blockchains. This document suggests a minimal light client design for the beacon chain.
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `CompactValidator` | `uint64` | compact representation of a validator for light clients |
## Constants
| Name | Value |
| - | - |
| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH` | `4` |
| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_INDEX` | **TBD** |
| `PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH` | `5` |
| `PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_INDEX` | **TBD** |
## Containers
### `LightClientUpdate`
```python
class LightClientUpdate(Container):
# Shard block root (and authenticating signature data)
shard_block_root: Root
fork_version: Version
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
signature: BLSSignature
# Updated beacon header (and authenticating branch)
header: BeaconBlockHeader
header_branch: Vector[Bytes32, BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH]
# Updated period committee (and authenticating branch)
committee: CompactCommittee
committee_branch: Vector[Bytes32, PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH + log_2(SHARD_COUNT)]
```
## Helpers
### `LightClientMemory`
```python
@dataclass
class LightClientMemory(object):
shard: Shard # Randomly initialized and retained forever
header: BeaconBlockHeader # Beacon header which is not expected to revert
# period committees corresponding to the beacon header
previous_committee: CompactCommittee
current_committee: CompactCommittee
next_committee: CompactCommittee
```
### `get_persistent_committee_pubkeys_and_balances`
```python
def get_persistent_committee_pubkeys_and_balances(memory: LightClientMemory,
epoch: Epoch) -> Tuple[Sequence[BLSPubkey], Sequence[uint64]]:
"""
Return pubkeys and balances for the persistent committee at ``epoch``.
"""
current_period = compute_epoch_at_slot(memory.header.slot) // EPOCHS_PER_SHARD_PERIOD
next_period = epoch // EPOCHS_PER_SHARD_PERIOD
assert next_period in (current_period, current_period + 1)
if next_period == current_period:
earlier_committee, later_committee = memory.previous_committee, memory.current_committee
else:
earlier_committee, later_committee = memory.current_committee, memory.next_committee
pubkeys = []
balances = []
for pubkey, compact_validator in zip(earlier_committee.pubkeys, earlier_committee.compact_validators):
index, slashed, balance = unpack_compact_validator(compact_validator)
if epoch % EPOCHS_PER_SHARD_PERIOD < index % EPOCHS_PER_SHARD_PERIOD:
pubkeys.append(pubkey)
balances.append(balance)
for pubkey, compact_validator in zip(later_committee.pubkeys, later_committee.compact_validators):
index, slashed, balance = unpack_compact_validator(compact_validator)
if epoch % EPOCHS_PER_SHARD_PERIOD >= index % EPOCHS_PER_SHARD_PERIOD:
pubkeys.append(pubkey)
balances.append(balance)
return pubkeys, balances
```
## Light client state updates
The state of a light client is stored in a `memory` object of type `LightClientMemory`. To advance its state a light client requests an `update` object of type `LightClientUpdate` from the network by sending a request containing `(memory.shard, memory.header.slot, slot_range_end)` and calls `update_memory(memory, update)`.
```python
def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None:
# Verify the update does not skip a period
current_period = compute_epoch_at_slot(memory.header.slot) // EPOCHS_PER_SHARD_PERIOD
next_epoch = compute_epoch_of_shard_slot(update.header.slot)
next_period = next_epoch // EPOCHS_PER_SHARD_PERIOD
assert next_period in (current_period, current_period + 1)
# Verify update header against shard block root and header branch
assert is_valid_merkle_branch(
leaf=hash_tree_root(update.header),
branch=update.header_branch,
depth=BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH,
index=BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_INDEX,
root=update.shard_block_root,
)
# Verify persistent committee votes pass 2/3 threshold
pubkeys, balances = get_persistent_committee_pubkeys_and_balances(memory, next_epoch)
assert 3 * sum(filter(lambda i: update.aggregation_bits[i], balances)) > 2 * sum(balances)
# Verify shard attestations
pubkeys = filter(lambda i: update.aggregation_bits[i], pubkeys)
domain = compute_domain(DOMAIN_SHARD_ATTESTER, update.fork_version)
signing_root = compute_signing_root(update.shard_block_root, domain)
assert bls.FastAggregateVerify(pubkeys, signing_root, update.signature)
# Update period committees if entering a new period
if next_period == current_period + 1:
assert is_valid_merkle_branch(
leaf=hash_tree_root(update.committee),
branch=update.committee_branch,
depth=PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH + log_2(SHARD_COUNT),
index=PERIOD_COMMITTEE_ROOT_IN_BEACON_STATE_INDEX << log_2(SHARD_COUNT) + memory.shard,
root=hash_tree_root(update.header),
)
memory.previous_committee = memory.current_committee
memory.current_committee = memory.next_committee
memory.next_committee = update.committee
# Update header
memory.header = update.header
```
## Data overhead
Once every `EPOCHS_PER_SHARD_PERIOD` epochs (~27 hours) a light client downloads a `LightClientUpdate` object:
* `shard_block_root`: 32 bytes
* `fork_version`: 4 bytes
* `aggregation_bits`: 16 bytes
* `signature`: 96 bytes
* `header`: 8 + 32 + 32 + 32 + 96 = 200 bytes
* `header_branch`: 4 * 32 = 128 bytes
* `committee`: 128 * (48 + 8) = 7,168 bytes
* `committee_branch`: (5 + 10) * 32 = 480 bytes
The total overhead is 8,124 bytes, or ~0.083 bytes per second. The Bitcoin SPV equivalent is 80 bytes per ~560 seconds, or ~0.143 bytes per second. Various compression optimisations (similar to [these](https://github.com/RCasatta/compressedheaders)) are possible.
A light client can choose to update the header (without updating the committee) more frequently than once every `EPOCHS_PER_SHARD_PERIOD` epochs at a cost of 32 + 4 + 16 + 96 + 200 + 128 = 476 bytes per update.

View File

@ -1,178 +0,0 @@
# Ethereum 2.0 Phase 1 -- Beacon Chain + Shard Chain Fork Choice
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [`get_forkchoice_shard_store`](#get_forkchoice_shard_store)
- [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance)
- [`get_shard_head`](#get_shard_head)
- [`get_shard_ancestor`](#get_shard_ancestor)
- [`get_pending_shard_blocks`](#get_pending_shard_blocks)
- [Handlers](#handlers)
- [`on_shard_block`](#on_shard_block)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase 1. It assumes the [beacon chain fork choice spec](./fork-choice.md).
## Fork choice
### Helpers
#### `get_forkchoice_shard_store`
```python
def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> ShardStore:
return ShardStore(
shard=shard,
signed_blocks={
anchor_state.shard_states[shard].latest_block_root: SignedShardBlock(
message=ShardBlock(slot=compute_previous_slot(anchor_state.slot), shard=shard)
)
},
block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]},
)
```
#### `get_shard_latest_attesting_balance`
```python
def get_shard_latest_attesting_balance(store: Store, shard: Shard, root: Root) -> Gwei:
shard_store = store.shard_stores[shard]
state = store.checkpoint_states[store.justified_checkpoint]
active_indices = get_active_validator_indices(state, get_current_epoch(state))
return Gwei(sum(
state.validators[i].effective_balance for i in active_indices
if (
i in shard_store.latest_messages
# TODO: check the latest message logic: currently, validator's previous vote of another shard
# would be ignored once their newer vote is accepted. Check if it makes sense.
and get_shard_ancestor(
store,
shard,
shard_store.latest_messages[i].root,
shard_store.signed_blocks[root].message.slot,
) == root
)
))
```
#### `get_shard_head`
```python
def get_shard_head(store: Store, shard: Shard) -> Root:
# Execute the LMD-GHOST fork choice
"""
Execute the LMD-GHOST fork choice.
"""
shard_store = store.shard_stores[shard]
beacon_head_root = get_head(store)
shard_head_state = store.block_states[beacon_head_root].shard_states[shard]
shard_head_root = shard_head_state.latest_block_root
shard_blocks = {
root: signed_shard_block.message for root, signed_shard_block in shard_store.signed_blocks.items()
if signed_shard_block.message.slot > shard_head_state.slot
}
while True:
# Find the valid child block roots
children = [
root for root, shard_block in shard_blocks.items()
if shard_block.shard_parent_root == shard_head_root
]
if len(children) == 0:
return shard_head_root
# Sort by latest attesting balance with ties broken lexicographically
shard_head_root = max(
children, key=lambda root: (get_shard_latest_attesting_balance(store, shard, root), root)
)
```
#### `get_shard_ancestor`
```python
def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Root:
shard_store = store.shard_stores[shard]
block = shard_store.signed_blocks[root].message
if block.slot > slot:
return get_shard_ancestor(store, shard, block.shard_parent_root, slot)
elif block.slot == slot:
return root
else:
# root is older than queried slot, thus a skip slot. Return most recent root prior to slot
return root
```
#### `get_pending_shard_blocks`
```python
def get_pending_shard_blocks(store: Store, shard: Shard) -> Sequence[SignedShardBlock]:
"""
Return the canonical shard block branch that has not yet been crosslinked.
"""
shard_store = store.shard_stores[shard]
beacon_head_root = get_head(store)
beacon_head_state = store.block_states[beacon_head_root]
latest_shard_block_root = beacon_head_state.shard_states[shard].latest_block_root
shard_head_root = get_shard_head(store, shard)
root = shard_head_root
signed_shard_blocks = []
while root != latest_shard_block_root:
signed_shard_block = shard_store.signed_blocks[root]
signed_shard_blocks.append(signed_shard_block)
root = signed_shard_block.message.shard_parent_root
signed_shard_blocks.reverse()
return signed_shard_blocks
```
### Handlers
#### `on_shard_block`
```python
def on_shard_block(store: Store, signed_shard_block: SignedShardBlock) -> None:
shard_block = signed_shard_block.message
shard = shard_block.shard
shard_store = store.shard_stores[shard]
# Check shard parent exists
assert shard_block.shard_parent_root in shard_store.block_states
shard_parent_state = shard_store.block_states[shard_block.shard_parent_root]
# Check beacon parent exists
assert shard_block.beacon_parent_root in store.block_states
beacon_parent_state = store.block_states[shard_block.beacon_parent_root]
# Check that block is later than the finalized shard state slot (optimization to reduce calls to get_ancestor)
finalized_beacon_state = store.block_states[store.finalized_checkpoint.root]
finalized_shard_state = finalized_beacon_state.shard_states[shard]
assert shard_block.slot > finalized_shard_state.slot
# Check block is a descendant of the finalized block at the checkpoint finalized slot
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
assert (
get_ancestor(store, shard_block.beacon_parent_root, finalized_slot) == store.finalized_checkpoint.root
)
# Check the block is valid and compute the post-state
shard_state = shard_parent_state.copy()
shard_state_transition(shard_state, signed_shard_block, beacon_parent_state, validate_result=True)
# Add new block to the store
# Note: storing `SignedShardBlock` format for computing `ShardTransition.proposer_signature_aggregate`
shard_store.signed_blocks[hash_tree_root(shard_block)] = signed_shard_block
# Add new state for this block to the store
shard_store.block_states[hash_tree_root(shard_block)] = shard_state
```

View File

@ -1,145 +0,0 @@
# Ethereum 2.0 Phase 1 -- Shard Transition and Fraud Proofs
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Helper functions](#helper-functions)
- [Shard block verification functions](#shard-block-verification-functions)
- [`verify_shard_block_message`](#verify_shard_block_message)
- [`verify_shard_block_signature`](#verify_shard_block_signature)
- [Shard state transition function](#shard-state-transition-function)
- [Fraud proofs](#fraud-proofs)
- [Verifying the proof](#verifying-the-proof)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
This document describes the shard transition function and fraud proofs as part of Phase 1 of Ethereum 2.0.
## Helper functions
### Shard block verification functions
#### `verify_shard_block_message`
```python
def verify_shard_block_message(beacon_parent_state: BeaconState,
shard_parent_state: ShardState,
block: ShardBlock) -> bool:
# Check `shard_parent_root` field
assert block.shard_parent_root == shard_parent_state.latest_block_root
# Check `beacon_parent_root` field
beacon_parent_block_header = beacon_parent_state.latest_block_header.copy()
if beacon_parent_block_header.state_root == Root():
beacon_parent_block_header.state_root = hash_tree_root(beacon_parent_state)
beacon_parent_root = hash_tree_root(beacon_parent_block_header)
assert block.beacon_parent_root == beacon_parent_root
# Check `slot` field
shard = block.shard
next_slot = Slot(block.slot + 1)
offset_slots = compute_offset_slots(get_latest_slot_for_shard(beacon_parent_state, shard), next_slot)
assert block.slot in offset_slots
# Check `proposer_index` field
assert block.proposer_index == get_shard_proposer_index(beacon_parent_state, block.slot, shard)
# Check `body` field
assert 0 < len(block.body) <= MAX_SHARD_BLOCK_SIZE
return True
```
#### `verify_shard_block_signature`
```python
def verify_shard_block_signature(beacon_parent_state: BeaconState,
signed_block: SignedShardBlock) -> bool:
proposer = beacon_parent_state.validators[signed_block.message.proposer_index]
domain = get_domain(beacon_parent_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot))
signing_root = compute_signing_root(signed_block.message, domain)
return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
```
## Shard state transition function
The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_state_transition(shard_state, signed_block, beacon_parent_state)`, where `beacon_parent_state` is the parent beacon state of the `signed_block`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid.
```python
def shard_state_transition(shard_state: ShardState,
signed_block: SignedShardBlock,
beacon_parent_state: BeaconState,
validate_result: bool = True) -> None:
assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message)
if validate_result:
assert verify_shard_block_signature(beacon_parent_state, signed_block)
process_shard_block(shard_state, signed_block.message)
```
```python
def process_shard_block(shard_state: ShardState,
block: ShardBlock) -> None:
"""
Update ``shard_state`` with shard ``block``.
"""
shard_state.slot = block.slot
prev_gasprice = shard_state.gasprice
shard_block_length = len(block.body)
shard_state.gasprice = compute_updated_gasprice(prev_gasprice, uint64(shard_block_length))
if shard_block_length != 0:
shard_state.latest_block_root = hash_tree_root(block)
```
## Fraud proofs
### Verifying the proof
TODO. The intent is to have a single universal fraud proof type, which contains the following parts:
1. An on-time attestation `attestation` on some shard `shard` signing a `transition: ShardTransition`
2. An index `offset_index` of a particular position to focus on
3. The `transition: ShardTransition` itself
4. The full body of the shard block `shard_block`
5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing
6. The `subkey` to generate the custody bit
Call the following function to verify the proof:
```python
def is_valid_fraud_proof(beacon_state: BeaconState,
attestation: Attestation,
offset_index: uint64,
transition: ShardTransition,
block: ShardBlock,
subkey: BLSPubkey,
beacon_parent_block: BeaconBlock) -> bool:
# 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`.
custody_bits = attestation.custody_bits_blocks
for j in range(len(custody_bits[offset_index])):
if custody_bits[offset_index][j] != generate_custody_bit(subkey, block):
return True
# 2. Check if the shard state transition result is wrong between
# `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`.
if offset_index == 0:
shard_states = beacon_parent_block.body.shard_transitions[attestation.data.shard].shard_states
shard_state = shard_states[len(shard_states) - 1]
else:
shard_state = transition.shard_states[offset_index - 1] # Not doing the actual state updates here.
process_shard_block(shard_state, block)
if shard_state != transition.shard_states[offset_index]:
return True
return False
```
```python
def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool:
# TODO
...
```

View File

@ -1,562 +0,0 @@
# Ethereum 2.0 Phase 1 -- Honest Validator
**Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 1](./), which describes the expected actions of a "validator" participating in the Ethereum 2.0 Phase 1 protocol.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Constants](#constants)
- [Misc](#misc)
- [Becoming a validator](#becoming-a-validator)
- [Beacon chain validator assignments](#beacon-chain-validator-assignments)
- [Lookahead](#lookahead)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal)
- [Preparing for a `BeaconBlock`](#preparing-for-a-beaconblock)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Custody slashings](#custody-slashings)
- [Custody key reveals](#custody-key-reveals)
- [Early derived secret reveals](#early-derived-secret-reveals)
- [Shard transitions](#shard-transitions)
- [Light client fields](#light-client-fields)
- [Packaging into a `SignedBeaconBlock`](#packaging-into-a-signedbeaconblock)
- [Attesting](#attesting)
- [`FullAttestationData`](#fullattestationdata)
- [`FullAttestation`](#fullattestation)
- [Timing](#timing)
- [Attestation data](#attestation-data)
- [Shard head root](#shard-head-root)
- [Shard transition](#shard-transition)
- [Construct attestation](#construct-attestation)
- [Attestation Aggregation](#attestation-aggregation)
- [Broadcast aggregate](#broadcast-aggregate)
- [`AggregateAndProof`](#aggregateandproof)
- [`SignedAggregateAndProof`](#signedaggregateandproof)
- [Light client committee](#light-client-committee)
- [Preparation](#preparation)
- [Light client vote](#light-client-vote)
- [Light client vote data](#light-client-vote-data)
- [`LightClientVoteData`](#lightclientvotedata)
- [Construct vote](#construct-vote)
- [`LightClientVote`](#lightclientvote)
- [Broadcast](#broadcast)
- [Light client vote aggregation](#light-client-vote-aggregation)
- [Aggregation selection](#aggregation-selection)
- [Construct aggregate](#construct-aggregate)
- [Broadcast aggregate](#broadcast-aggregate-1)
- [`LightAggregateAndProof`](#lightaggregateandproof)
- [`SignedLightAggregateAndProof`](#signedlightaggregateandproof)
- [How to avoid slashing](#how-to-avoid-slashing)
- [Custody slashing](#custody-slashing)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document represents the expected behavior of an "honest validator" with respect to Phase 1 of the Ethereum 2.0 protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope.
A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof-of-work networks in which miners provide collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol.
## Prerequisites
This document is an extension of the [Phase 0 -- Validator](../phase0/validator.md). All behaviors and definitions defined in the Phase 0 doc carry over unless explicitly noted or overridden.
All terminology, constants, functions, and protocol mechanics defined in the [Phase 1 -- The Beacon Chain](./beacon-chain.md) and [Phase 1 -- Custody Game](./custody-game.md) docs are requisite for this document and used throughout. Please see the Phase 1 docs before continuing and use them as a reference throughout.
## Constants
See constants from [Phase 0 validator guide](../phase0/validator.md#constants).
### Misc
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT` | `2**3` (= 8) | validators | |
| `LIGHT_CLIENT_PREPARATION_EPOCHS` | `2**2` (= 4) | epochs | |
## Becoming a validator
Becoming a validator in Phase 1 is unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#becoming-a-validator) for details.
## Beacon chain validator assignments
Beacon chain validator assignments to beacon committees and beacon block proposal are unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#validator-assignments) for details.
### Lookahead
Lookahead for beacon committee assignments operates in the same manner as Phase 0, but committee members must join a shard block pubsub topic in addition to the committee attestation topic.
Specifically _after_ finding stable peers of attestation subnets (see Phase 0) a validator should:
* Let `shard = compute_shard_from_committee_index(state, committee_index, slot)`
* Subscribe to the pubsub topic `shard_{shard}_block` (attestation subnet peers should have this topic available).
TODO: For now, the `state` we pass to `compute_shard_from_committee_index` is the current state without considering `len(state.shard_states)`, i.e., the result from `get_active_shard_count(state)` changes. We should fix it when we have shard count update logic.
## Beacon chain responsibilities
A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attesting). Proposals happen infrequently, whereas attestations should be created once per epoch.
These responsibilities are largely unchanged from Phase 0, but utilize the updated `SignedBeaconBlock`, `BeaconBlock`, `BeaconBlockBody`, `Attestation`, and `AttestationData` definitions found in Phase 1. Below notes only the additional and modified behavior with respect to Phase 0.
Phase 1 adds light client committees and associated responsibilities, discussed [below](#light-client-committee).
### Block proposal
#### Preparing for a `BeaconBlock`
`slot`, `proposer_index`, `parent_root`, `state_root` fields are unchanged.
#### Constructing the `BeaconBlockBody`
`randao_reveal`, `eth1_data`, and `graffiti` are unchanged.
`proposer_slashings`, `deposits`, and `voluntary_exits` are unchanged.
`attester_slashings` and `attestations` operate exactly as in Phase 0, but with new definitations of `AttesterSlashing` and `Attestation`, along with modified validation conditions found in `process_attester_slashing` and `process_attestation`.
##### Custody slashings
Up to `MAX_CUSTODY_SLASHINGS`, [`CustodySlashing`](./custody-game.md#custodyslashing) objects can be included in the `block`. The custody slashings must satisfy the verification conditions found in [custody slashings processing](./custody-game.md#custody-slashings). The validator receives a small "whistleblower" reward for each custody slashing included (THIS IS NOT CURRENTLY THE CASE BUT PROBABLY SHOULD BE).
##### Custody key reveals
Up to `MAX_CUSTODY_KEY_REVEALS`, [`CustodyKeyReveal`](./custody-game.md#custodykeyreveal) objects can be included in the `block`. The custody key reveals must satisfy the verification conditions found in [custody key reveal processing](./custody-game.md#custody-key-reveals). The validator receives a small reward for each custody key reveal included.
##### Early derived secret reveals
Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./custody-game.md#earlyderivedsecretreveal) objects can be included in the `block`. The early derived secret reveals must satisfy the verification conditions found in [early derived secret reveal processing](./custody-game.md#custody-key-reveals). The validator receives a small "whistleblower" reward for each early derived secrete reveal included.
##### Shard transitions
Exactly `MAX_SHARDS` [`ShardTransition`](./beacon-chain.md#shardtransition) objects are included in the block. Default each to an empty `ShardTransition()`. Then for each committee assigned to the slot with an associated `committee_index` and `shard`, set `shard_transitions[shard] = full_transitions[winning_root]` if the committee had enough weight to form a crosslink this slot.
Specifically:
* Call `shards, winning_roots = get_shard_winning_roots(state, block.body.attestations)`
* Let `full_transitions` be a dictionary mapping from the `shard_transition_root`s found in `attestations` to the corresponding full `ShardTransition`
* Then for each `shard` and `winning_root` in `zip(shards, winning_roots)` set `shard_transitions[shard] = full_transitions[winning_root]`
*Note*: The `state` passed into `get_shard_winning_roots` must be transitioned the slot of `block.slot` to run accurately due to the internal use of `get_online_validator_indices` and `is_on_time_attestation`.
```python
def get_shard_winning_roots(state: BeaconState,
attestations: Sequence[Attestation]) -> Tuple[Sequence[Shard], Sequence[Root]]:
shards = []
winning_roots = []
online_indices = get_online_validator_indices(state)
on_time_attestation_slot = compute_previous_slot(state.slot)
committee_count = get_committee_count_per_slot(state, compute_epoch_at_slot(on_time_attestation_slot))
for committee_index in map(CommitteeIndex, range(committee_count)):
shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot)
# All attestations in the block for this committee/shard and are "on time"
shard_attestations = [
attestation for attestation in attestations
if is_on_time_attestation(state, attestation.data) and attestation.data.index == committee_index
]
committee = get_beacon_committee(state, on_time_attestation_slot, committee_index)
# Loop over all shard transition roots, looking for a winning root
shard_transition_roots = set(a.data.shard_transition_root for a in shard_attestations) # non-duplicate
for shard_transition_root in sorted(shard_transition_roots):
transition_attestations = [
a for a in shard_attestations
if a.data.shard_transition_root == shard_transition_root
]
transition_participants: Set[ValidatorIndex] = set()
for attestation in transition_attestations:
participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
transition_participants = transition_participants.union(participants)
enough_online_stake = (
get_total_balance(state, online_indices.intersection(transition_participants)) * 3 >=
get_total_balance(state, online_indices.intersection(committee)) * 2
)
if enough_online_stake:
shards.append(shard)
winning_roots.append(shard_transition_root)
break
return shards, winning_roots
```
##### Light client fields
First retrieve `best_aggregate` from `get_best_light_client_aggregate(block, aggregates)` where `aggregates` is a list of valid aggregated `LightClientVote`s for the previous slot.
Then:
* Set `light_client_bits = best_aggregate.aggregation_bits`
* Set `light_client_signature = best_aggregate.signature`
```python
def get_best_light_client_aggregate(block: BeaconBlock,
aggregates: Sequence[LightClientVote]) -> LightClientVote:
viable_aggregates = [
aggregate for aggregate in aggregates
if (
aggregate.data.slot == compute_previous_slot(block.slot)
and aggregate.data.beacon_block_root == block.parent_root
)
]
return max(
viable_aggregates,
# Ties broken by lexicographically by hash_tree_root
key=lambda a: (len([i for i in a.aggregation_bits if i == 1]), hash_tree_root(a)),
default=LightClientVote(),
)
```
#### Packaging into a `SignedBeaconBlock`
Packaging into a `SignedBeaconBlock` is unchanged from Phase 0.
### Attesting
A validator is expected to create, sign, and broadcast an attestation during each epoch.
Assignments and the core of this duty are unchanged from Phase 0. There are a few additional fields related to the assigned shard chain.
The `Attestation` and `AttestationData` defined in the [Phase 1 Beacon Chain spec](./beacon-chain.md) utilizes `shard_transition_root: Root` rather than a full `ShardTransition`. For the purposes of the validator and p2p layer, a modified `FullAttestationData` and containing `FullAttestation` are used to send the accompanying `ShardTransition` in its entirety. Note that due to the properties of SSZ `hash_tree_root`, the root and signatures of `AttestationData` and `FullAttestationData` are equivalent.
#### `FullAttestationData`
```python
class FullAttestationData(Container):
slot: Slot
index: CommitteeIndex
# LMD GHOST vote
beacon_block_root: Root
# FFG vote
source: Checkpoint
target: Checkpoint
# Current-slot shard block root
shard_head_root: Root
# Full shard transition
shard_transition: ShardTransition
```
#### `FullAttestation`
```python
class FullAttestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
data: FullAttestationData
signature: BLSSignature
```
#### Timing
Note the timing of when to create/broadcast is altered from Phase 1.
A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid `BeaconBlock` from the expected beacon block proposer and a valid `ShardBlock` for the expected shard block proposer for the assigned `slot` or (b) one-half of the `slot` has transpired (`SECONDS_PER_SLOT / 2` seconds after the start of `slot`) -- whichever comes _first_.
#### Attestation data
`attestation_data` is constructed in the same manner as Phase 0 but uses `FullAttestationData` with the addition of two fields -- `shard_head_root` and `shard_transition`.
- Let `head_block` be the result of running the fork choice during the assigned slot.
- Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`.
- Let `shard_head_block` be the result of running the fork choice on the assigned shard chain during the assigned slot.
- Let `shard_blocks` be the shard blocks in the chain starting immediately _after_ the most recent crosslink (`head_state.shard_transitions[shard].latest_block_root`) up to the `shard_head_block` (i.e. the value of the shard fork choice store of `get_pending_shard_blocks(store, shard_store)`).
*Note*: We assume that the fork choice only follows branches with valid `offset_slots` with respect to the most recent beacon state shard transition for the queried shard.
##### Shard head root
If `attestation_data.slot == GENESIS_SLOT`, set `attestation_data.shard_head_root = Root()`. Otherwise, set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`.
##### Shard transition
Set `shard_transition` to the value returned by `get_shard_transition(head_state, shard, shard_blocks)`.
```python
def get_shard_transition_fields(
beacon_state: BeaconState,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock],
) -> Tuple[Sequence[uint64], Sequence[Root], Sequence[ShardState]]:
shard_block_lengths = [] # type: PyList[uint64]
shard_data_roots = [] # type: PyList[Root]
shard_states = [] # type: PyList[ShardState]
shard_state = beacon_state.shard_states[shard]
shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks]
offset_slots = compute_offset_slots(
get_latest_slot_for_shard(beacon_state, shard),
Slot(beacon_state.slot + 1),
)
for slot in offset_slots:
if slot in shard_block_slots:
shard_block = shard_blocks[shard_block_slots.index(slot)]
shard_data_roots.append(hash_tree_root(shard_block.message.body))
else:
shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard))
shard_data_roots.append(Root())
shard_state = shard_state.copy()
process_shard_block(shard_state, shard_block.message)
shard_states.append(shard_state)
shard_block_lengths.append(uint64(len(shard_block.message.body)))
return shard_block_lengths, shard_data_roots, shard_states
```
```python
def get_shard_transition(beacon_state: BeaconState,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition:
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if beacon_state.slot == GENESIS_SLOT:
return ShardTransition()
offset_slots = compute_offset_slots(
get_latest_slot_for_shard(beacon_state, shard),
Slot(beacon_state.slot + 1),
)
shard_block_lengths, shard_data_roots, shard_states = (
get_shard_transition_fields(beacon_state, shard, shard_blocks)
)
if len(shard_blocks) > 0:
proposer_signatures = [shard_block.signature for shard_block in shard_blocks]
proposer_signature_aggregate = bls.Aggregate(proposer_signatures)
else:
proposer_signature_aggregate = NO_SIGNATURE
return ShardTransition(
start_slot=offset_slots[0],
shard_block_lengths=shard_block_lengths,
shard_data_roots=shard_data_roots,
shard_states=shard_states,
proposer_signature_aggregate=proposer_signature_aggregate,
)
```
#### Construct attestation
Next, the validator creates `attestation`, a `FullAttestation` as defined above.
`attestation.data`, `attestation.aggregation_bits`, and `attestation.signature` are unchanged from Phase 0. But safety/validity in signing the message is premised upon calculation of the "custody bit" [TODO].
### Attestation Aggregation
Some validators are selected to locally aggregate attestations with a similar `attestation_data` to their constructed `attestation` for the assigned `slot`.
Aggregation selection and the core of this duty are largely unchanged from Phase 0. Any additional components or changes are noted.
#### Broadcast aggregate
Note the timing of when to broadcast aggregates is altered in Phase 1+.
If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) three-fourths of the way through the `slot` -- that is, `SECONDS_PER_SLOT * 3 / 4` seconds after the start of `slot`.
##### `AggregateAndProof`
`AggregateAndProof` is unchanged other than the contained `Attestation`.
```python
class AggregateAndProof(Container):
aggregator_index: ValidatorIndex
aggregate: Attestation
selection_proof: BLSSignature
```
##### `SignedAggregateAndProof`
`AggregateAndProof` is unchanged other than the contained `AggregateAndProof`.
```python
class SignedAggregateAndProof(Container):
message: AggregateAndProof
signature: BLSSignature
```
### Light client committee
In addition to the core beacon chain responsibilities, Phase 1 adds an additional role -- the Light Client Committee -- to aid in light client functionality.
Validators serve on the light client committee for `LIGHT_CLIENT_COMMITTEE_PERIOD` epochs and the assignment to be on a committee is known `LIGHT_CLIENT_COMMITTEE_PERIOD` epochs in advance.
#### Preparation
When `get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == LIGHT_CLIENT_COMMITTEE_PERIOD - LIGHT_CLIENT_PREPARATION_EPOCHS` each validator must check if they are in the next period light client committee by calling `is_in_next_light_client_committee()`.
If the validator is in the next light client committee, they must join the `light_client_votes` pubsub topic to begin duties at the start of the next period.
```python
def is_in_next_light_client_committee(state: BeaconState, index: ValidatorIndex) -> bool:
next_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD)
return index in next_committee
```
#### Light client vote
During a period of epochs that the validator is a part of the light client committee (`validator_index in get_light_client_committee(state, epoch)`), the validator creates and broadcasts a `LightClientVote` at each slot.
A validator should create and broadcast the `light_client_vote` to the `light_client_votes` pubsub topic when either (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the `slot` have transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_.
- Let `light_client_committee = get_light_client_committee(state, compute_epoch_at_slot(slot))`
##### Light client vote data
First the validator constructs `light_client_vote_data`, a [`LightClientVoteData`](#lightclientvotedata) object.
* Let `head_block` be the result of running the fork choice during the assigned slot.
* Set `light_client_vote.slot = slot`.
* Set `light_client_vote.beacon_block_root = hash_tree_root(head_block)`.
###### `LightClientVoteData`
```python
class LightClientVoteData(Container):
slot: Slot
beacon_block_root: Root
```
##### Construct vote
Then the validator constructs `light_client_vote`, a [`LightClientVote`](#lightclientvote) object.
* Set `light_client_vote.data = light_client_vote_data`.
* Set `light_client_vote.aggregation_bits` to be a `Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE]`, where the bit of the index of the validator in the `light_client_committee` is set to `0b1` and all other bits are are set to `0b0`.
* Set `light_client_vote.signature = vote_signature` where `vote_signature` is obtained from:
```python
def get_light_client_vote_signature(state: BeaconState,
light_client_vote_data: LightClientVoteData,
privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(light_client_vote_data.slot))
signing_root = compute_signing_root(light_client_vote_data, domain)
return bls.Sign(privkey, signing_root)
```
###### `LightClientVote`
```python
class LightClientVote(Container):
data: LightClientVoteData
aggregation_bits: Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE]
signature: BLSSignature
```
##### Broadcast
Finally, the validator broadcasts `light_client_vote` to the `light_client_votes` pubsub topic.
#### Light client vote aggregation
Some validators in the light client committee are selected to locally aggregate light client votes with a similar `light_client_vote_data` to their constructed `light_client_vote` for the assigned `slot`.
#### Aggregation selection
A validator is selected to aggregate based upon the return value of `is_light_client_aggregator()`.
```python
def get_light_client_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_LIGHT_SELECTION_PROOF, compute_epoch_at_slot(slot))
signing_root = compute_signing_root(slot, domain)
return bls.Sign(privkey, signing_root)
```
```python
def is_light_client_aggregator(state: BeaconState, slot: Slot, slot_signature: BLSSignature) -> bool:
committee = get_light_client_committee(state, compute_epoch_at_slot(slot))
modulo = max(1, len(committee) // TARGET_LIGHT_CLIENT_AGGREGATORS_PER_SLOT)
return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0
```
#### Construct aggregate
If the validator is selected to aggregate (`is_light_client_aggregator()`), they construct an aggregate light client vote via the following.
Collect `light_client_votes` seen via gossip during the `slot` that have an equivalent `light_client_vote_data` to that constructed by the validator, and create a `aggregate_light_client_vote: LightClientVote` with the following fields.
* Set `aggregate_light_client_vote.data = light_client_vote_data` where `light_client_vote_data` is the `LightClientVoteData` object that is the same for each individual light client vote being aggregated.
* Set `aggregate_light_client_vote.aggregation_bits` to be a `Bitvector[LIGHT_CLIENT_COMMITTEE_SIZE]`, where each bit set from each individual light client vote is set to `0b1`.
* Set `aggregate_light_client_vote.signature = aggregate_light_client_signature` where `aggregate_light_client_signature` is obtained from `get_aggregate_light_client_signature`.
```python
def get_aggregate_light_client_signature(light_client_votes: Sequence[LightClientVote]) -> BLSSignature:
signatures = [light_client_vote.signature for light_client_vote in light_client_votes]
return bls.Aggregate(signatures)
```
#### Broadcast aggregate
If the validator is selected to aggregate (`is_light_client_aggregator`), then they broadcast their best aggregate light client vote as a `SignedLightAggregateAndProof` to the global aggregate light client vote channel (`aggregate_light_client_votes`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.
Selection proofs are provided in `LightAggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator.
`LightAggregateAndProof` messages are signed by the aggregator and broadcast inside of `SignedLightAggregateAndProof` objects to prevent a class of DoS attacks and message forgeries.
First, `light_aggregate_and_proof = get_light_aggregate_and_proof(state, validator_index, aggregate_light_client_vote, privkey)` is constructed.
```python
def get_light_aggregate_and_proof(state: BeaconState,
aggregator_index: ValidatorIndex,
aggregate: LightClientVote,
privkey: int) -> LightAggregateAndProof:
return LightAggregateAndProof(
aggregator_index=aggregator_index,
aggregate=aggregate,
selection_proof=get_light_client_slot_signature(state, aggregate.data.slot, privkey),
)
```
Then `signed_light_aggregate_and_proof = SignedLightAggregateAndProof(message=light_aggregate_and_proof, signature=signature)` is constructed and broadast. Where `signature` is obtained from:
```python
def get_light_aggregate_and_proof_signature(state: BeaconState,
aggregate_and_proof: LightAggregateAndProof,
privkey: int) -> BLSSignature:
aggregate = aggregate_and_proof.aggregate
domain = get_domain(state, DOMAIN_LIGHT_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot))
signing_root = compute_signing_root(aggregate_and_proof, domain)
return bls.Sign(privkey, signing_root)
```
##### `LightAggregateAndProof`
```python
class LightAggregateAndProof(Container):
aggregator_index: ValidatorIndex
aggregate: LightClientVote
selection_proof: BLSSignature
```
##### `SignedLightAggregateAndProof`
```python
class SignedLightAggregateAndProof(Container):
message: LightAggregateAndProof
signature: BLSSignature
```
## How to avoid slashing
Proposer and Attester slashings described in Phase 0 remain in place with the
addition of the following.
### Custody slashing
To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret:
```python
def get_custody_secret(state: BeaconState,
validator_index: ValidatorIndex,
privkey: int,
epoch: Epoch=None) -> BLSSignature:
if epoch is None:
epoch = get_current_epoch(state)
period = get_custody_period_for_validator(validator_index, epoch)
epoch_to_sign = get_randao_epoch_for_custody_period(period, validator_index)
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
signing_root = compute_signing_root(Epoch(epoch_to_sign), domain)
return bls.Sign(privkey, signing_root)
```
Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated. While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing.

View File

@ -0,0 +1,688 @@
# Ethereum 2.0 Sharding -- Beacon Chain changes
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Custom types](#custom-types)
- [Constants](#constants)
- [Configuration](#configuration)
- [Misc](#misc)
- [Shard block configs](#shard-block-configs)
- [Precomputed size verification points](#precomputed-size-verification-points)
- [Gwei values](#gwei-values)
- [Time parameters](#time-parameters)
- [Domain types](#domain-types)
- [Updated containers](#updated-containers)
- [`AttestationData`](#attestationdata)
- [`BeaconBlockBody`](#beaconblockbody)
- [`BeaconState`](#beaconstate)
- [New containers](#new-containers)
- [`DataCommitment`](#datacommitment)
- [`ShardHeader`](#shardheader)
- [`SignedShardHeader`](#signedshardheader)
- [`PendingShardHeader`](#pendingshardheader)
- [Helper functions](#helper-functions)
- [Misc](#misc-1)
- [`next_power_of_two`](#next_power_of_two)
- [`compute_previous_slot`](#compute_previous_slot)
- [`compute_updated_gasprice`](#compute_updated_gasprice)
- [`compute_committee_source_epoch`](#compute_committee_source_epoch)
- [Beacon state accessors](#beacon-state-accessors)
- [Updated `get_committee_count_per_slot`](#updated-get_committee_count_per_slot)
- [`get_active_shard_count`](#get_active_shard_count)
- [`get_shard_committee`](#get_shard_committee)
- [`compute_proposer_index`](#compute_proposer_index)
- [`get_shard_proposer_index`](#get_shard_proposer_index)
- [`get_start_shard`](#get_start_shard)
- [`compute_shard_from_committee_index`](#compute_shard_from_committee_index)
- [`compute_committee_index_from_shard`](#compute_committee_index_from_shard)
- [Block processing](#block-processing)
- [Operations](#operations)
- [New Attestation processing](#new-attestation-processing)
- [Updated `process_attestation`](#updated-process_attestation)
- [`update_pending_votes`](#update_pending_votes)
- [`process_shard_header`](#process_shard_header)
- [Epoch transition](#epoch-transition)
- [Pending headers](#pending-headers)
- [Shard epoch increment](#shard-epoch-increment)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document describes the extensions made to the Phase 0 design of The Beacon Chain to support data sharding,
based on the ideas [here](https://hackmd.io/G-Iy5jqyT7CXWEz8Ssos8g) and more broadly [here](https://arxiv.org/abs/1809.09044),
using KZG10 commitments to commit to data to remove any need for fraud proofs (and hence, safety-critical synchrony assumptions) in the design.
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `Shard` | `uint64` | A shard number |
| `BLSCommitment` | `bytes48` | A G1 curve point |
| `BLSPoint` | `uint256` | A number `x` in the range `0 <= x < MODULUS` |
## Constants
The following values are (non-configurable) constants used throughout the specification.
| Name | Value | Notes |
| - | - | - |
| `PRIMITIVE_ROOT_OF_UNITY` | `5` | Primitive root of unity of the BLS12_381 (inner) modulus |
| `DATA_AVAILABILITY_INVERSE_CODING_RATE` | `2**1` (= 2) | Factor by which samples are extended for data availability encoding |
| `POINTS_PER_SAMPLE` | `uint64(2**3)` (= 8) | 31 * 8 = 248 bytes |
| `MODULUS` | `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` (curve order of BLS12_381) |
## Configuration
### Misc
| Name | Value | Notes |
| - | - | - |
| `MAX_SHARDS` | `uint64(2**10)` (= 1,024) | Theoretical max shard count (used to determine data structure sizes) |
| `INITIAL_ACTIVE_SHARDS` | `uint64(2**6)` (= 64) | Initial shard count |
| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `uint64(2**3)` (= 8) | Gasprice may decrease/increase by at most exp(1 / this value) *per epoch* |
| `MAX_SHARD_HEADERS_PER_SHARD` | `4` | |
### Shard block configs
| Name | Value | Notes |
| - | - | - |
| `MAX_SAMPLES_PER_BLOCK` | `uint64(2**11)` (= 2,048) | 248 * 2,048 = 507,904 bytes |
| `TARGET_SAMPLES_PER_BLOCK` | `uint64(2**10)` (= 1,024) | 248 * 1,024 = 253,952 bytes |
### Precomputed size verification points
| Name | Value |
| - | - |
| `G1_SETUP` | Type `List[G1]`. The G1-side trusted setup `[G, G*s, G*s**2....]`; note that the first point is the generator. |
| `G2_SETUP` | Type `List[G2]`. The G2-side trusted setup `[G, G*s, G*s**2....]` |
| `ROOT_OF_UNITY` | `pow(PRIMITIVE_ROOT_OF_UNITY, (MODULUS - 1) // (MAX_SAMPLES_PER_BLOCK * POINTS_PER_SAMPLE, MODULUS)` |
### Gwei values
| Name | Value | Unit | Description |
| - | - | - | - |
| `MAX_GASPRICE` | `Gwei(2**33)` (= 8,589,934,592) | Gwei | Max gasprice charged for a TARGET-sized shard block |
| `MIN_GASPRICE` | `Gwei(2**3)` (= 8) | Gwei | Min gasprice charged for a TARGET-sized shard block |
### Time parameters
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours |
### Domain types
| Name | Value |
| - | - |
| `DOMAIN_SHARD_HEADER` | `DomainType('0x80000000')` |
| `DOMAIN_SHARD_COMMITTEE` | `DomainType('0x81000000')` |
## Updated containers
The following containers have updated definitions to support Sharding.
### `AttestationData`
```python
class AttestationData(Container):
slot: Slot
index: CommitteeIndex
# LMD GHOST vote
beacon_block_root: Root
# FFG vote
source: Checkpoint
target: Checkpoint
# Shard header root
shard_header_root: Root # [New in Sharding]
```
### `BeaconBlockBody`
```python
class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body]
shard_headers: List[SignedShardHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD]
```
### `BeaconState`
```python
class BeaconState(merge.BeaconState): # [extends The Merge block body]
# [Updated fields]
previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
# [New fields]
previous_epoch_pending_shard_headers: List[PendingShardHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD * SLOTS_PER_EPOCH]
current_epoch_pending_shard_headers: List[PendingShardHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD * SLOTS_PER_EPOCH]
grandparent_epoch_confirmed_commitments: Vector[Vector[DataCommitment, SLOTS_PER_EPOCH], MAX_SHARDS]
shard_gasprice: uint64
current_epoch_start_shard: Shard
```
## New containers
The shard data itself is network-layer only, and can be found in the [P2P specification](./p2p-interface.md).
The beacon chain registers just the commitments of the shard data.
### `DataCommitment`
```python
class DataCommitment(Container):
# KZG10 commitment to the data
point: BLSCommitment
# Length of the data in samples
length: uint64
```
### `ShardHeader`
```python
class ShardHeader(Container):
# Slot and shard that this header is intended for
slot: Slot
shard: Shard
# The actual data commitment
commitment: DataCommitment
# Proof that the degree < commitment.length
degree_proof: BLSCommitment
```
TODO: add shard-proposer-index to shard headers, similar to optimization done with beacon-blocks.
### `SignedShardHeader`
```python
class SignedShardHeader(Container):
message: ShardHeader
signature: BLSSignature
```
### `PendingShardHeader`
```python
class PendingShardHeader(Container):
# Slot and shard that this header is intended for
slot: Slot
shard: Shard
# KZG10 commitment to the data
commitment: DataCommitment
# hash_tree_root of the ShardHeader (stored so that attestations can be checked against it)
root: Root
# Who voted for the header
votes: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
# Has this header been confirmed?
confirmed: bool
```
## Helper functions
### Misc
#### `next_power_of_two`
```python
def next_power_of_two(x):
return 2 ** ((x - 1).bit_length())
```
#### `compute_previous_slot`
```python
def compute_previous_slot(slot: Slot) -> Slot:
if slot > 0:
return Slot(slot - 1)
else:
return Slot(0)
```
#### `compute_updated_gasprice`
```python
def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint64, adjustment_quotient: uint64) -> Gwei:
if shard_block_length > TARGET_SAMPLES_PER_BLOCK:
delta = max(1, prev_gasprice * (shard_block_length - TARGET_SAMPLES_PER_BLOCK)
// TARGET_SAMPLES_PER_BLOCK // adjustment_quotient)
return min(prev_gasprice + delta, MAX_GASPRICE)
else:
delta = max(1, prev_gasprice * (TARGET_SAMPLES_PER_BLOCK - shard_block_length)
// TARGET_SAMPLES_PER_BLOCK // adjustment_quotient)
return max(prev_gasprice, MIN_GASPRICE + delta) - delta
```
#### `compute_committee_source_epoch`
```python
def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch:
"""
Return the source epoch for computing the committee.
"""
source_epoch = Epoch(epoch - epoch % period)
if source_epoch >= period:
source_epoch -= period # `period` epochs lookahead
return source_epoch
```
### Beacon state accessors
#### Updated `get_committee_count_per_slot`
```python
def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64:
"""
Return the number of committees in each slot for the given ``epoch``.
"""
return max(uint64(1), min(
get_active_shard_count(state, epoch),
uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
))
```
#### `get_active_shard_count`
```python
def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64:
"""
Return the number of active shards.
Note that this puts an upper bound on the number of committees per slot.
"""
return INITIAL_ACTIVE_SHARDS
```
#### `get_shard_committee`
```python
def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]:
"""
Return the shard committee of the given ``epoch`` of the given ``shard``.
"""
source_epoch = compute_committee_source_epoch(epoch, SHARD_COMMITTEE_PERIOD)
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
return compute_committee(
indices=active_validator_indices,
seed=seed,
index=shard,
count=get_active_shard_count(beacon_state, epoch),
)
```
#### `compute_proposer_index`
Updated version to get a proposer index that will only allow proposers with a certain minimum balance,
ensuring that the balance is always sufficient to cover gas costs.
```python
def compute_proposer_index(beacon_state: BeaconState,
indices: Sequence[ValidatorIndex],
seed: Bytes32,
min_effective_balance: GWei = GWei(0)) -> ValidatorIndex:
"""
Return from ``indices`` a random index sampled by effective balance.
"""
assert len(indices) > 0
MAX_RANDOM_BYTE = 2**8 - 1
i = uint64(0)
total = uint64(len(indices))
while True:
candidate_index = indices[compute_shuffled_index(i % total, total, seed)]
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
effective_balance = beacon_state.validators[candidate_index].effective_balance
if effective_balance <= min_effective_balance:
continue
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
return candidate_index
i += 1
```
#### `get_shard_proposer_index`
```python
def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
"""
Return the proposer's index of shard block at ``slot``.
"""
epoch = compute_epoch_at_slot(slot)
committee = get_shard_committee(beacon_state, epoch, shard)
seed = hash(get_seed(beacon_state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(beacon_state.slot))
# Proposer must have sufficient balance to pay for worst case fee burn
EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION = (
(EFFECTIVE_BALANCE_INCREMENT - EFFECTIVE_BALANCE_INCREMENT)
* HYSTERESIS_DOWNWARD_MULTIPLIER // HYSTERESIS_QUOTIENT
)
min_effective_balance = (
beacon_state.shard_gasprice * MAX_SAMPLES_PER_BLOCK // TARGET_SAMPLES_PER_BLOCK
+ EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION
)
return compute_proposer_index(beacon_state, committee, seed, min_effective_balance)
```
#### `get_start_shard`
```python
def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
"""
Return the start shard at ``slot``.
"""
current_epoch_start_slot = compute_start_slot_at_epoch(get_current_epoch(state))
shard = state.current_epoch_start_shard
if slot > current_epoch_start_slot:
# Current epoch or the next epoch lookahead
for _slot in range(current_epoch_start_slot, slot):
committee_count = get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(_slot)))
active_shard_count = get_active_shard_count(state, compute_epoch_at_slot(Slot(_slot)))
shard = (shard + committee_count) % active_shard_count
elif slot < current_epoch_start_slot:
# Previous epoch
for _slot in list(range(slot, current_epoch_start_slot))[::-1]:
committee_count = get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(_slot)))
active_shard_count = get_active_shard_count(state, compute_epoch_at_slot(Slot(_slot)))
# Ensure positive
shard = (shard + active_shard_count - committee_count) % active_shard_count
return Shard(shard)
```
#### `compute_shard_from_committee_index`
```python
def compute_shard_from_committee_index(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Shard:
active_shards = get_active_shard_count(state, compute_epoch_at_slot(slot))
return Shard((index + get_start_shard(state, slot)) % active_shards)
```
#### `compute_committee_index_from_shard`
```python
def compute_committee_index_from_shard(state: BeaconState, slot: Slot, shard: Shard) -> CommitteeIndex:
active_shards = get_active_shard_count(state, compute_epoch_at_slot(slot))
return CommitteeIndex((active_shards + shard - get_start_shard(state, slot)) % active_shards)
```
### Block processing
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body) # [Modified in Sharding]
process_application_payload(state, block.body) # [New in Merge]
```
#### Operations
```python
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
# Verify that outstanding deposits are processed up to the maximum number of deposits
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
# Limit is dynamic based on active shard count
assert len(body.shard_headers) <= MAX_SHARD_HEADERS_PER_SHARD * get_active_shard_count(state, get_current_epoch(state))
for_ops(body.shard_headers, process_shard_header)
# New attestation processing
for_ops(body.attestations, process_attestation)
for_ops(body.deposits, process_deposit)
for_ops(body.voluntary_exits, process_voluntary_exit)
```
### New Attestation processing
#### Updated `process_attestation`
```python
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
phase0.process_attestation(state, attestation)
update_pending_votes(state, attestation)
```
#### `update_pending_votes`
```python
def update_pending_votes(state: BeaconState, attestation: Attestation) -> None:
# Find and update the PendingShardHeader object, invalid block if pending header not in state
if compute_epoch_at_slot(attestation.data.slot) == get_current_epoch(state):
pending_headers = state.current_epoch_pending_shard_headers
else:
pending_headers = state.previous_epoch_pending_shard_headers
pending_header = None
for header in pending_headers:
if header.root == attestation.data.shard_header_root:
pending_header = header
assert pending_header is not None
assert pending_header.slot == attestation.data.slot
assert pending_header.shard == compute_shard_from_committee_index(
state,
attestation.data.slot,
attestation.data.index,
)
for i in range(len(pending_header.votes)):
pending_header.votes[i] = pending_header.votes[i] or attestation.aggregation_bits[i]
# Check if the PendingShardHeader is eligible for expedited confirmation
# Requirement 1: nothing else confirmed
all_candidates = [
c for c in pending_headers if
(c.slot, c.shard) == (pending_header.slot, pending_header.shard)
]
if True in [c.confirmed for c in all_candidates]:
return
# Requirement 2: >= 2/3 of balance attesting
participants = get_attesting_indices(state, attestation.data, pending_header.votes)
participants_balance = get_total_balance(state, participants)
full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
full_committee_balance = get_total_balance(state, full_committee)
if participants_balance * 3 >= full_committee_balance * 2:
pending_header.confirmed = True
```
#### `process_shard_header`
```python
def process_shard_header(state: BeaconState,
signed_header: SignedShardHeader) -> None:
header = signed_header.message
header_root = hash_tree_root(header)
assert compute_epoch_at_slot(header.slot) in [get_previous_epoch(state), get_current_epoch(state)]
# Verify signature
signer_index = get_shard_proposer_index(state, header.slot, header.shard)
signing_root = compute_signing_root(header, get_domain(state, DOMAIN_SHARD_HEADER))
assert bls.Verify(state.validators[signer_index].pubkey, signing_root, signed_header.signature)
# Verify the length by verifying the degree.
if header.commitment.length == 0:
assert header.degree_proof == G1_SETUP[0]
assert (
bls.Pairing(header.degree_proof, G2_SETUP[0])
== bls.Pairing(header.commitment.point, G2_SETUP[-header.commitment.length]))
)
# Get the correct pending header list
if compute_epoch_at_slot(header.slot) == get_current_epoch(state):
pending_headers = state.current_epoch_pending_shard_headers
else:
pending_headers = state.previous_epoch_pending_shard_headers
# Check that this header is not yet in the pending list
assert header_root not in [pending_header.root for pending_header in pending_headers]
# Include it in the pending list
index = compute_committee_index_from_shard(state, header.slot, header.shard)
committee_length = len(get_beacon_committee(state, header.slot, index))
pending_headers.append(PendingShardHeader(
slot=header.slot,
shard=header.shard,
commitment=header.commitment,
root=header_root,
votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length),
confirmed=False,
))
```
The degree proof works as follows. For a block `B` with length `l` (so `l` values in `[0...l - 1]`, seen as a polynomial `B(X)` which takes these values),
the length proof is the commitment to the polynomial `B(X) * X**(MAX_DEGREE + 1 - l)`,
where `MAX_DEGREE` is the maximum power of `s` available in the setup, which is `MAX_DEGREE = len(G2_SETUP) - 1`.
The goal is to ensure that a proof can only be constructed if `deg(B) < l` (there are not hidden higher-order terms in the polynomial, which would thwart reconstruction).
### Epoch transition
This epoch transition overrides the Merge epoch transition:
```python
def process_epoch(state: BeaconState) -> None:
process_justification_and_finalization(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
# Sharding
process_pending_headers(state)
process_confirmed_header_fees(state)
reset_pending_headers(state)
# Final updates
# Phase 0
process_eth1_data_reset(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_roots_update(state)
process_participation_record_updates(state)
process_shard_epoch_increment(state)
```
#### Pending headers
```python
def process_pending_headers(state: BeaconState) -> None:
# Pending header processing applies to the previous epoch.
# Skip if `GENESIS_EPOCH` because no prior epoch to process.
if get_current_epoch(state) == GENESIS_EPOCH:
return
previous_epoch_start_slot = compute_start_slot_at_epoch(get_previous_epoch(state))
for slot in range(previous_epoch_start_slot, previous_epoch_start_slot + SLOTS_PER_EPOCH):
for shard in range(get_active_shard_count(state)):
# Pending headers for this (slot, shard) combo
candidates = [
c for c in state.previous_epoch_pending_shard_headers
if (c.slot, c.shard) == (slot, shard)
]
# The entire committee (and its balance)
full_committee = get_beacon_committee(state, slot, shard)
full_committee_balance = get_total_balance(state, full_committee)
# If any candidates already confirmed, skip
if True in [c.confirmed for c in candidates]:
continue
# The set of voters who voted for each header (and their total balances)
voting_sets = [
[v for i, v in enumerate(full_committee) if c.votes[i]]
for c in candidates
]
voting_balances = [
get_total_balance(state, voters)
for voters in voting_sets
]
# Get the index with the most total balance voting for them.
# NOTE: if two choices get exactly the same voting balance,
# the candidate earlier in the list wins
if max(voting_balances) > 0:
winning_index = voting_balances.index(max(voting_balances))
else:
# If no votes, zero wins
winning_index = [c.root for c in candidates].index(Root())
candidates[winning_index].confirmed = True
for slot_index in range(SLOTS_PER_EPOCH):
for shard in range(SHARD_COUNT):
state.grandparent_epoch_confirmed_commitments[shard][slot_index] = DataCommitment()
confirmed_headers = [candidate in state.previous_epoch_pending_shard_headers if candidate.confirmed]
for header in confirmed_headers:
state.grandparent_epoch_confirmed_commitments[c.shard][c.slot % SLOTS_PER_EPOCH] = c.commitment
```
```python
def charge_confirmed_header_fees(state: BeaconState) -> None:
new_gasprice = state.shard_gasprice
adjustment_quotient = (
get_active_shard_count(state, get_current_epoch(state))
* SLOTS_PER_EPOCH * GASPRICE_ADJUSTMENT_COEFFICIENT
)
previous_epoch_start_slot = compute_start_slot_at_epoch(get_previous_epoch(state))
for slot in range(previous_epoch_start_slot, previous_epoch_start_slot + SLOTS_PER_EPOCH):
for shard in range(SHARD_COUNT):
confirmed_candidates = [
c for c in state.previous_epoch_pending_shard_headers
if (c.slot, c.shard, c.confirmed) == (slot, shard, True)
]
if not any(confirmed_candidates):
continue
candidate = confirmed_candidates[0]
# Charge EIP 1559 fee
proposer = get_shard_proposer(state, slot, shard)
fee = (
(state.shard_gasprice * candidate.commitment.length)
// TARGET_SAMPLES_PER_BLOCK
)
decrease_balance(state, proposer, fee)
# Track updated gas price
new_gasprice = compute_updated_gasprice(
new_gasprice,
candidate.commitment.length,
adjustment_quotient,
)
state.shard_gasprice = new_gasprice
```
```python
def reset_pending_headers(state: BeaconState) -> None:
state.previous_epoch_pending_shard_headers = state.current_epoch_pending_shard_headers
state.current_epoch_pending_shard_headers = []
# Add dummy "empty" PendingShardHeader (default vote for if no shard header available)
next_epoch = get_current_epoch(state) + 1
next_epoch_start_slot = compute_start_slot_at_epoch(next_epoch)
for slot in range(next_epoch_start_slot, next_epoch_start_slot + SLOTS_IN_EPOCH):
for index in range(get_committee_count_per_slot(next_epoch):
shard = compute_shard_from_committee_index(state, slot, index)
committee_length = len(get_beacon_committee(state, slot, shard))
state.current_epoch_pending_shard_headers.append(PendingShardHeader(
slot=slot,
shard=shard,
commitment=DataCommitment(),
root=Root(),
votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length),
confirmed=False,
))
```
#### Shard epoch increment
```python
def process_shard_epoch_increment(state: BeaconState) -> None:
# Update current_epoch_start_shard
state.current_epoch_start_shard = get_start_shard(state, Slot(state.slot + 1))
```

View File

@ -0,0 +1,94 @@
# Ethereum 2.0 Sharding -- Network specification
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [New containers](#new-containers)
- [ShardBlob](#shardblob)
- [SignedShardBlob](#signedshardblob)
- [Gossip domain](#gossip-domain)
- [Topics and messages](#topics-and-messages)
- [Shard blobs: `shard_blob_{shard}`](#shard-blobs-shard_blob_shard)
- [Shard header: `shard_header`](#shard-header-shard_header)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
The specification of these changes continues in the same format as the [Phase0](../phase0/p2p-interface.md) and
[Altair](../altair/p2p-interface.md) network specifications, and assumes them as pre-requisite.
The adjustments and additions for Shards are outlined in this document.
## New containers
### ShardBlob
Network-only.
```python
class ShardBlob(Container):
# Slot and shard that this blob is intended for
slot: Slot
shard: Shard
# The actual data. Represented in header as data commitment and degree proof
data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOCK]
```
Note that the hash-tree-root of the `ShardBlob` does not match the `ShardHeader`,
since the blob deals with full data, whereas the header includes the KZG commitment and degree proof instead.
### SignedShardBlob
Network-only.
```python
class SignedShardBlob(Container):
message: ShardBlob
# The signature, the message is the commitment on the blob
signature: BLSSignature
```
## Gossip domain
### Topics and messages
Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface.md#topics-and-messages), names and payload types are:
| Name | Message Type |
|----------------------------------|---------------------------|
| `shard_blob_{shard}` | `SignedShardBlob` |
| `shard_header` | `SignedShardHeader` |
The [DAS network specification](./das-p2p.md) defines additional topics.
#### Shard blobs: `shard_blob_{shard}`
Shard block data, in the form of a `SignedShardBlob` is published to the `shard_blob_{shard}` subnets.
The following validations MUST pass before forwarding the `signed_blob` (with inner `blob`) on the horizontal subnet or creating samples for it.
- _[REJECT]_ `blob.shard` MUST match the topic `{shard}` parameter. (And thus within valid shard index range)
- _[IGNORE]_ The `blob` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. validate that `blob.slot <= current_slot`
(a client MAY queue future blobs for processing at the appropriate slot).
- _[IGNORE]_ The blob is the first blob with valid signature received for the proposer for the `(slot, shard)` combination.
- _[REJECT]_ As already limited by the SSZ list-limit, it is important the blob is well-formatted and not too large.
- _[REJECT]_ The `blob.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid.
- _[REJECT]_ The proposer signature, `signed_blob.signature`, is valid with respect to the `proposer_index` pubkey, signed over the SSZ output of `commit_to_data(blob.data)`.
- _[REJECT]_ The blob is proposed by the expected `proposer_index` for the blob's slot.
TODO: make double blob proposals slashable?
#### Shard header: `shard_header`
Shard header data, in the form of a `SignedShardHeader` is published to the global `shard_header` subnet.
TODO: validation conditions.

View File

@ -17,8 +17,8 @@ def apply_constants_config(spec_globals: Dict[str, Any], warn_if_unknown: bool =
# Keep the same type as the default value indicates (which may be an SSZ basic type subclass, e.g. 'Gwei')
spec_globals[k] = spec_globals[k].__class__(v)
else:
# Note: Phase 0 spec will not know the phase 1 config values.
# Yet, during debugging you can enable explicit warnings.
# Note: The phase 0 spec will not warn if Altair or later config values are applied.
# During debugging you can enable explicit warnings.
if warn_if_unknown:
print(f"WARNING: unknown config key: '{k}' with value: '{v}'")

View File

@ -12,7 +12,7 @@ from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature,
)
from eth2spec.test.context import (
PHASE0, PHASE1,
PHASE0,
MAINNET, MINIMAL,
expect_assertion_error,
with_all_phases_except,
@ -60,7 +60,7 @@ def get_committee_indices(spec, state, duplicates=False):
state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index])
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
def test_invalid_signature_missing_participant(spec, state):
@ -82,7 +82,7 @@ def test_invalid_signature_missing_participant(spec, state):
yield from run_sync_committee_processing(spec, state, block, expect_exception=True)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
def test_invalid_signature_extra_participant(spec, state):
@ -191,7 +191,7 @@ def run_successful_sync_committee_test(spec, state, committee, committee_bits):
)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@with_configs([MINIMAL], reason="to create nonduplicate committee")
@spec_state_test
def test_sync_committee_rewards_nonduplicate_committee(spec, state):
@ -207,7 +207,7 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state):
yield from run_successful_sync_committee_test(spec, state, committee, committee_bits)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@with_configs([MAINNET], reason="to create duplicate committee")
@spec_state_test
def test_sync_committee_rewards_duplicate_committee(spec, state):
@ -223,7 +223,7 @@ def test_sync_committee_rewards_duplicate_committee(spec, state):
yield from run_successful_sync_committee_test(spec, state, committee, committee_bits)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
def test_sync_committee_rewards_not_full_participants(spec, state):
@ -234,7 +234,7 @@ def test_sync_committee_rewards_not_full_participants(spec, state):
yield from run_successful_sync_committee_test(spec, state, committee, committee_bits)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
def test_invalid_signature_past_block(spec, state):
@ -273,7 +273,7 @@ def test_invalid_signature_past_block(spec, state):
yield from run_sync_committee_processing(spec, state, invalid_block, expect_exception=True)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@with_configs([MINIMAL], reason="to produce different committee sets")
@spec_state_test
@always_bls
@ -310,7 +310,7 @@ def test_invalid_signature_previous_committee(spec, state):
yield from run_sync_committee_processing(spec, state, block, expect_exception=True)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@with_configs([MINIMAL], reason="too slow")

View File

@ -1,5 +1,5 @@
from eth2spec.test.context import (
PHASE0, PHASE1,
PHASE0,
MINIMAL,
spec_state_test,
with_all_phases_except,
@ -11,7 +11,7 @@ from eth2spec.test.helpers.epoch_processing import (
)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_sync_committees_progress(spec, state):

View File

@ -12,7 +12,7 @@ from eth2spec.test.helpers.sync_committee import (
compute_aggregate_sync_committee_signature,
)
from eth2spec.test.context import (
PHASE0, PHASE1,
PHASE0,
with_all_phases_except,
spec_state_test,
)
@ -40,46 +40,46 @@ def run_sync_committee_sanity_test(spec, state, fraction_full=1.0):
yield 'post', state
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_full_sync_committee_committee(spec, state):
next_epoch(spec, state)
yield from run_sync_committee_sanity_test(spec, state, fraction_full=1.0)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_half_sync_committee_committee(spec, state):
next_epoch(spec, state)
yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_empty_sync_committee_committee(spec, state):
next_epoch(spec, state)
yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_full_sync_committee_committee_genesis(spec, state):
yield from run_sync_committee_sanity_test(spec, state, fraction_full=1.0)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_half_sync_committee_committee_genesis(spec, state):
yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_empty_sync_committee_committee_genesis(spec, state):
yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0)
@with_all_phases_except([PHASE0, PHASE1])
@with_all_phases_except([PHASE0])
@spec_state_test
def test_inactivity_scores(spec, state):
for _ in range(spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2):

View File

@ -1,7 +1,6 @@
import pytest
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.altair import spec as spec_altair
from eth2spec.utils import bls
@ -19,7 +18,6 @@ from importlib import reload
def reload_specs():
reload(spec_phase0)
reload(spec_phase1)
reload(spec_altair)
@ -29,10 +27,15 @@ SpecForkName = NewType("SpecForkName", str)
ConfigName = NewType("ConfigName", str)
PHASE0 = SpecForkName('phase0')
PHASE1 = SpecForkName('phase1')
ALTAIR = SpecForkName('altair')
ALL_PHASES = (PHASE0, PHASE1, ALTAIR)
# Experimental phases (not included in default "ALL_PHASES"):
MERGE = SpecForkName('merge')
SHARDING = SpecForkName('sharding')
CUSTODY_GAME = SpecForkName('custody_game')
DAS = SpecForkName('das')
ALL_PHASES = (PHASE0, ALTAIR)
MAINNET = ConfigName('mainnet')
MINIMAL = ConfigName('minimal')
@ -54,18 +57,12 @@ class SpecPhase0(Spec):
...
class SpecPhase1(Spec):
...
class SpecAltair(Spec):
...
# add transfer, bridge, etc. as the spec evolves
class SpecForks(TypedDict, total=False):
PHASE0: SpecPhase0
PHASE1: SpecPhase1
ALTAIR: SpecAltair
@ -78,11 +75,8 @@ def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Ca
state = create_genesis_state(spec=p0, validator_balances=balances,
activation_threshold=activation_threshold)
if spec.fork == PHASE1:
# TODO: instead of upgrading a test phase0 genesis state we can also write a phase1 state helper.
# Decide based on performance/consistency results later.
state = phases[PHASE1].upgrade_to_phase1(state)
elif spec.fork == ALTAIR:
# TODO: upgrade to merge spec, and later sharding.
if spec.fork == ALTAIR:
state = phases[ALTAIR].upgrade_to_altair(state)
return state
@ -337,30 +331,34 @@ def with_phases(phases, other_phases=None):
return None
run_phases = [phase]
if PHASE0 not in run_phases and ALTAIR not in run_phases:
dump_skipping_message("none of the recognized phases are executable, skipping test.")
return None
available_phases = set(run_phases)
if other_phases is not None:
available_phases |= set(other_phases)
# TODO: test state is dependent on phase0 but is immediately transitioned to phase1.
# A new state-creation helper for phase 1 may be in place, and then phase1+ tests can run without phase0
# TODO: test state is dependent on phase0 but is immediately transitioned to later phases.
# A new state-creation helper for later phases may be in place, and then tests can run without phase0
available_phases.add(PHASE0)
# Populate all phases for multi-phase tests
phase_dir = {}
if PHASE0 in available_phases:
phase_dir[PHASE0] = spec_phase0
if PHASE1 in available_phases:
phase_dir[PHASE1] = spec_phase1
if ALTAIR in available_phases:
phase_dir[ALTAIR] = spec_altair
# return is ignored whenever multiple phases are ran. If
# return is ignored whenever multiple phases are ran.
# This return is for test generators to emit python generators (yielding test vector outputs)
if PHASE0 in run_phases:
ret = fn(spec=spec_phase0, phases=phase_dir, *args, **kw)
if PHASE1 in run_phases:
ret = fn(spec=spec_phase1, phases=phase_dir, *args, **kw)
if ALTAIR in run_phases:
ret = fn(spec=spec_altair, phases=phase_dir, *args, **kw)
# TODO: merge, sharding, custody_game and das are not executable yet.
# Tests that specify these features will not run, and get ignored for these specific phases.
return ret
return wrapper
return decorator
@ -382,23 +380,9 @@ def with_configs(configs, reason=None):
return decorator
def only_full_crosslink(fn):
def is_full_crosslink(spec, state):
epoch = spec.compute_epoch_at_slot(state.slot)
return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state)
def wrapper(*args, spec: Spec, state: Any, **kw):
# TODO: update condition to "phase1+" if we have phase2
if spec.fork == PHASE1 and not is_full_crosslink(spec, state):
dump_skipping_message("only for full crosslink")
return None
return fn(*args, spec=spec, state=state, **kw)
return wrapper
def is_post_altair(spec):
if spec.fork in [PHASE0, PHASE1]:
# TODO: PHASE1 fork is temporarily parallel to ALTAIR.
# Will make PHASE1 fork inherit ALTAIR later.
# TODO: everything runs in parallel to Altair.
# After features are rebased on the Altair fork, this can be reduced to just PHASE0.
if spec.fork in [PHASE0, MERGE, SHARDING, CUSTODY_GAME, DAS]:
return False
return True

View File

@ -1,7 +1,6 @@
from eth2spec.test.context import (
PHASE0,
ALTAIR,
with_all_phases_except,
CUSTODY_GAME,
with_phases,
spec_state_test,
always_bls,
)
@ -13,7 +12,7 @@ from eth2spec.test.helpers.attestations import (
)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_on_time_success(spec, state):
@ -24,7 +23,7 @@ def test_on_time_success(spec, state):
yield from run_attestation_processing(spec, state, attestation)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_late_success(spec, state):

View File

@ -8,13 +8,12 @@ from eth2spec.test.helpers.attestations import (
)
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
PHASE0,
ALTAIR,
CUSTODY_GAME,
MINIMAL,
expect_assertion_error,
disable_process_reveal_deadlines,
spec_state_test,
with_all_phases_except,
with_phases,
with_configs,
)
from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
@ -69,7 +68,7 @@ def run_custody_chunk_response_processing(spec, state, custody_response, valid=T
yield 'post', state
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
@disable_process_reveal_deadlines
@ -93,7 +92,7 @@ def test_challenge_appended(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -119,7 +118,7 @@ def test_challenge_empty_element_replaced(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -145,7 +144,7 @@ def test_duplicate_challenge(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -173,7 +172,7 @@ def test_second_challenge(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge1)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -198,7 +197,7 @@ def test_multiple_epochs_custody(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -223,7 +222,7 @@ def test_many_epochs_custody(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -244,7 +243,7 @@ def test_off_chain_attestation(spec, state):
yield from run_chunk_challenge_processing(spec, state, challenge)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -276,7 +275,7 @@ def test_custody_response(spec, state):
yield from run_custody_chunk_response_processing(spec, state, custody_response)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -307,7 +306,7 @@ def test_custody_response_chunk_index_2(spec, state):
yield from run_custody_chunk_response_processing(spec, state, custody_response)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -339,7 +338,7 @@ def test_custody_response_multiple_epochs(spec, state):
yield from run_custody_chunk_response_processing(spec, state, custody_response)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")

View File

@ -1,8 +1,7 @@
from eth2spec.test.helpers.custody import get_valid_custody_key_reveal
from eth2spec.test.context import (
PHASE0,
ALTAIR,
with_all_phases_except,
CUSTODY_GAME,
with_phases,
spec_state_test,
expect_assertion_error,
always_bls,
@ -40,7 +39,7 @@ def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=Tru
yield 'post', state
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_success(spec, state):
@ -50,7 +49,7 @@ def test_success(spec, state):
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_reveal_too_early(spec, state):
@ -59,7 +58,7 @@ def test_reveal_too_early(spec, state):
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_wrong_period(spec, state):
@ -68,7 +67,7 @@ def test_wrong_period(spec, state):
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_late_reveal(spec, state):
@ -78,7 +77,7 @@ def test_late_reveal(spec, state):
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_double_reveal(spec, state):

View File

@ -9,10 +9,9 @@ from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.ssz.ssz_typing import ByteList
from eth2spec.test.helpers.state import get_balance, transition_to
from eth2spec.test.context import (
PHASE0,
MINIMAL,
ALTAIR,
with_all_phases_except,
CUSTODY_GAME,
with_phases,
spec_state_test,
expect_assertion_error,
disable_process_reveal_deadlines,
@ -113,7 +112,7 @@ def run_standard_custody_slashing_test(spec,
yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -121,7 +120,7 @@ def test_custody_slashing(spec, state):
yield from run_standard_custody_slashing_test(spec, state)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -129,7 +128,7 @@ def test_incorrect_custody_slashing(spec, state):
yield from run_standard_custody_slashing_test(spec, state, correct=False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -137,7 +136,7 @@ def test_multiple_epochs_custody(spec, state):
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
@ -145,7 +144,7 @@ def test_many_epochs_custody(spec, state):
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 5)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")

View File

@ -1,9 +1,8 @@
from eth2spec.test.helpers.custody import get_valid_early_derived_secret_reveal
from eth2spec.test.helpers.state import next_epoch_via_block, get_balance
from eth2spec.test.context import (
PHASE0,
ALTAIR,
with_all_phases_except,
CUSTODY_GAME,
with_phases,
spec_state_test,
expect_assertion_error,
always_bls,
@ -42,7 +41,7 @@ def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, v
yield 'post', state
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_success(spec, state):
@ -51,7 +50,7 @@ def test_success(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@never_bls
def test_reveal_from_current_epoch(spec, state):
@ -60,7 +59,7 @@ def test_reveal_from_current_epoch(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@never_bls
def test_reveal_from_past_epoch(spec, state):
@ -70,7 +69,7 @@ def test_reveal_from_past_epoch(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_reveal_with_custody_padding(spec, state):
@ -82,7 +81,7 @@ def test_reveal_with_custody_padding(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, True)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@always_bls
def test_reveal_with_custody_padding_minus_one(spec, state):
@ -94,7 +93,7 @@ def test_reveal_with_custody_padding_minus_one(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, True)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@never_bls
def test_double_reveal(spec, state):
@ -115,7 +114,7 @@ def test_double_reveal(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal2, False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@never_bls
def test_revealer_is_slashed(spec, state):
@ -125,7 +124,7 @@ def test_revealer_is_slashed(spec, state):
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@never_bls
def test_far_future_epoch(spec, state):

View File

@ -7,17 +7,16 @@ from eth2spec.test.helpers.attestations import (
)
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
PHASE0,
ALTAIR,
CUSTODY_GAME,
MINIMAL,
spec_state_test,
with_all_phases_except,
with_phases,
with_configs,
)
from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import (
from eth2spec.test.custody_game.block_processing.test_process_chunk_challenge import (
run_chunk_challenge_processing,
)
@ -26,7 +25,7 @@ def run_process_challenge_deadlines(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines')
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_validator_slashed_after_chunk_challenge(spec, state):

View File

@ -1,6 +1,5 @@
from eth2spec.test.context import (
PHASE0,
ALTAIR,
CUSTODY_GAME,
)
from eth2spec.test.helpers.custody import (
get_valid_chunk_challenge,
@ -13,24 +12,26 @@ from eth2spec.test.helpers.attestations import (
)
from eth2spec.test.helpers.state import next_epoch_via_block, transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
with_all_phases_except,
with_phases,
spec_state_test,
)
from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import (
from eth2spec.test.custody_game.block_processing.test_process_chunk_challenge import (
run_chunk_challenge_processing,
run_custody_chunk_response_processing,
)
from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
from eth2spec.test.custody_game.block_processing.test_process_custody_key_reveal import (
run_custody_key_reveal_processing,
)
def run_process_custody_final_updates(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates')
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
def test_validator_withdrawal_delay(spec, state):
transition_to_valid_shard_slot(spec, state)
@ -43,7 +44,7 @@ def test_validator_withdrawal_delay(spec, state):
assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
def test_validator_withdrawal_reenable_after_custody_reveal(spec, state):
transition_to_valid_shard_slot(spec, state)
@ -68,7 +69,7 @@ def test_validator_withdrawal_reenable_after_custody_reveal(spec, state):
assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state):
transition_to_valid_shard_slot(spec, state)
@ -117,7 +118,7 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state):
assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state):
transition_to_valid_shard_slot(spec, state)

View File

@ -3,22 +3,23 @@ from eth2spec.test.helpers.custody import (
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.context import (
PHASE0,
ALTAIR,
CUSTODY_GAME,
MINIMAL,
with_all_phases_except,
with_phases,
with_configs,
spec_state_test,
)
from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with
from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing
from eth2spec.test.custody_game.block_processing.test_process_custody_key_reveal import (
run_custody_key_reveal_processing,
)
def run_process_challenge_deadlines(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines')
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_validator_slashed_after_reveal_deadline(spec, state):
@ -38,7 +39,7 @@ def test_validator_slashed_after_reveal_deadline(spec, state):
assert state.validators[0].slashed == 1
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_validator_not_slashed_after_reveal(spec, state):

View File

@ -1,12 +1,10 @@
from typing import Dict, Sequence
from eth2spec.test.context import (
PHASE0,
ALTAIR,
CUSTODY_GAME,
MINIMAL,
with_all_phases_except,
with_phases,
spec_state_test,
only_full_crosslink,
with_configs,
)
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
@ -43,99 +41,13 @@ def run_beacon_block(spec, state, block, valid=True):
yield 'post', state
#
# Beacon block with non-empty shard transitions
#
def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True):
transition_to(spec, state, state.slot + target_len_offset_slot)
body = get_sample_shard_block_body(spec, is_max=True)
shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True)
shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}
shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
attestations = [
get_valid_on_time_attestation(
spec,
state,
index=committee_index,
shard_transition=shard_transitions[shard],
signed=True,
)
for shard in shard_block_dict.keys()
]
beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
beacon_block.body.attestations = attestations
beacon_block.body.shard_transitions = shard_transitions
pre_gasprice = state.shard_states[shard].gasprice
pre_shard_states = state.shard_states.copy()
yield 'pre', state.copy()
if not valid:
state_transition_and_sign_block(spec, state, beacon_block, expect_fail=True)
yield 'block', beacon_block
yield 'post', None
return
signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block)
yield 'block', signed_beacon_block
yield 'post', state
for shard in range(spec.get_active_shard_count(state)):
post_shard_state = state.shard_states[shard]
if shard in shard_block_dict:
# Shard state has been changed to state_transition result
assert post_shard_state == shard_transitions[shard].shard_states[
len(shard_transitions[shard].shard_states) - 1
]
assert post_shard_state.slot == state.slot - 1
if len((shard_block_dict[shard])) == 0:
# `latest_block_root` is the same
assert post_shard_state.latest_block_root == pre_shard_states[shard].latest_block_root
if target_len_offset_slot == 1 and len(shard_block_dict[shard]) > 0:
assert post_shard_state.gasprice > pre_gasprice
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_process_beacon_block_with_normal_shard_transition(spec, state):
transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
committee_index = spec.CommitteeIndex(0)
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
assert state.shard_states[shard].slot == state.slot - 1
yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_process_beacon_block_with_empty_proposal_transition(spec, state):
transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
committee_index = spec.CommitteeIndex(0)
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1)
assert state.shard_states[shard].slot == state.slot - 1
yield from run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard)
#
# Beacon block with custody operations
#
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@only_full_crosslink
def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
transition_to_valid_shard_slot(spec, state)
@ -167,7 +79,7 @@ def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
yield from run_beacon_block(spec, state, block)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@with_configs([MINIMAL])
def test_custody_key_reveal(spec, state):
@ -181,7 +93,7 @@ def test_custody_key_reveal(spec, state):
yield from run_beacon_block(spec, state, block)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
def test_early_derived_secret_reveal(spec, state):
transition_to_valid_shard_slot(spec, state)
@ -192,9 +104,8 @@ def test_early_derived_secret_reveal(spec, state):
yield from run_beacon_block(spec, state, block)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([CUSTODY_GAME])
@spec_state_test
@only_full_crosslink
def test_custody_slashing(spec, state):
transition_to_valid_shard_slot(spec, state)

View File

@ -2,10 +2,9 @@ from lru import LRU
from typing import List
from eth2spec.test.context import expect_assertion_error, PHASE1, is_post_altair
from eth2spec.test.context import expect_assertion_error, is_post_altair
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_typing import Bitlist
@ -51,7 +50,7 @@ def run_attestation_processing(spec, state, attestation, valid=True):
yield 'post', state
def build_attestation_data(spec, state, slot, index, shard=None, shard_transition=None, on_time=True):
def build_attestation_data(spec, state, slot, index, shard=None, on_time=True):
assert state.slot >= slot
if slot == state.slot:
@ -82,32 +81,11 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio
target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root),
)
if spec.fork == PHASE1:
if shard is None:
shard = spec.compute_shard_from_committee_index(state, data.index, data.slot)
data.shard = shard
if shard_transition is not None:
last_offset_index = len(shard_transition.shard_data_roots) - 1
data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
data.shard_transition_root = shard_transition.hash_tree_root()
else:
if on_time:
if data.slot == spec.GENESIS_SLOT:
data.shard_head_root = spec.Root()
data.shard_transition_root = spec.ShardTransition().hash_tree_root()
else:
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
last_offset_index = len(shard_transition.shard_data_roots) - 1
data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
data.shard_transition_root = shard_transition.hash_tree_root()
else:
data.shard_head_root = state.shard_states[shard].latest_block_root
data.shard_transition_root = spec.Root()
# if spec.fork == SHARDING # TODO: add extra data for shard voting
return data
def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False):
'''
Construct on-time attestation for next slot
'''
@ -121,13 +99,12 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_tran
state,
slot=slot,
index=index,
shard_transition=shard_transition,
signed=signed,
on_time=True,
)
def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False, shard_transition=None):
def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False):
'''
Construct on-time attestation for next slot
'''
@ -137,7 +114,7 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False,
index = 0
return get_valid_attestation(spec, state, slot=slot, index=index,
signed=signed, on_time=False, shard_transition=shard_transition)
signed=signed, on_time=False)
def get_valid_attestation(spec,
@ -145,7 +122,6 @@ def get_valid_attestation(spec,
slot=None,
index=None,
filter_participant_set=None,
shard_transition=None,
signed=False,
on_time=True):
# If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed.
@ -156,7 +132,7 @@ def get_valid_attestation(spec,
index = 0
attestation_data = build_attestation_data(
spec, state, slot=slot, index=index, shard_transition=shard_transition, on_time=on_time
spec, state, slot=slot, index=index, on_time=on_time
)
beacon_committee = spec.get_beacon_committee(
@ -258,16 +234,11 @@ def next_epoch_with_attestations(spec,
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)):
for index in range(committees_per_slot):
if spec.fork == PHASE1:
shard = spec.compute_shard_from_committee_index(post_state, index, slot_to_attest)
shard_transition = get_shard_transition_of_committee(spec, post_state, index)
block.body.shard_transitions[shard] = shard_transition
else:
shard_transition = None
# if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block
cur_attestation = get_valid_attestation(
spec, post_state, slot_to_attest,
shard_transition=shard_transition, index=index, signed=True, on_time=True
index=index, signed=True, on_time=True
)
block.body.attestations.append(cur_attestation)

View File

@ -16,6 +16,8 @@ def get_process_calls(spec):
lambda state, block: for_ops(state, block.body.proposer_slashings, spec.process_proposer_slashing),
'process_attester_slashing':
lambda state, block: for_ops(state, block.body.attester_slashings, spec.process_attester_slashing),
'process_shard_header':
lambda state, block: for_ops(state, block.body.shard_headers, spec.process_shard_header),
'process_attestation':
lambda state, block: for_ops(state, block.body.attestations, spec.process_attestation),
'process_deposit':
@ -25,12 +27,12 @@ def get_process_calls(spec):
# Altair
'process_sync_committee':
lambda state, block: spec.process_sync_committee(state, block.body.sync_aggregate),
# PHASE1
# Merge
'process_application_payload':
lambda state, block: spec.process_application_payload(state, block.body),
# Custody Game
'process_custody_game_operations':
lambda state, block: spec.process_custody_game_operations(state, block.body),
'process_shard_transitions':
lambda state, block: spec.process_shard_transitions(
state, block.body.shard_transitions, block.body.attestations),
}

View File

@ -3,14 +3,20 @@ from eth2spec.test.context import is_post_altair
def get_process_calls(spec):
# unrecognized processing functions will be ignored.
# This sums up the aggregate of processing functions of all phases.
# Note: make sure to explicitly remove/override a processing function in later phases,
# or the old function will stick around.
return [
# PHASE0
'process_justification_and_finalization',
'process_rewards_and_penalties',
'process_registry_updates',
'process_reveal_deadlines',
'process_challenge_deadlines',
'process_reveal_deadlines', # custody game
'process_challenge_deadlines', # custody game
'process_slashings',
'process_pending_header.', # sharding
'charge_confirmed_header_fees', # sharding
'reset_pending_headers', # sharding
'process_eth1_data_reset',
'process_effective_balance_updates',
'process_slashings_reset',
@ -21,8 +27,7 @@ def get_process_calls(spec):
'process_participation_record_updates'
),
'process_sync_committee_updates',
# PHASE1
'process_phase_1_final_updates',
'process_shard_epoch_increment' # sharding
]

View File

@ -1,37 +0,0 @@
from eth2spec.test.context import expect_assertion_error
def run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=True):
"""
Run ``process_shard_transitions``, yielding:
- pre-state ('pre')
- shard_transitions ('shard_transitions')
- attestations ('attestations')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# yield pre-state
yield 'pre', state
yield 'shard_transitions', shard_transitions
yield 'attestations', attestations
# If the attestation is invalid, processing is aborted, and there is no post-state.
if not valid:
expect_assertion_error(lambda: spec.process_shard_transitions(state, shard_transitions, attestations))
yield 'post', None
return
# process crosslinks
spec.process_shard_transitions(state, shard_transitions, attestations)
# yield post-state
yield 'post', state
def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks=None):
if shard_blocks is None:
shard_blocks = []
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks)
return shard_transition

View File

@ -42,9 +42,9 @@ def transition_to_slot_via_block(spec, state, slot):
def transition_to_valid_shard_slot(spec, state):
"""
Transition to slot `spec.PHASE_1_FORK_SLOT + 1` and fork at `spec.PHASE_1_FORK_SLOT`.
Transition to slot `spec.SHARDING_FORK_SLOT + 1` and fork at `spec.SHARDING_FORK_SLOT`.
"""
transition_to(spec, state, spec.PHASE_1_FORK_SLOT)
transition_to(spec, state, spec.SHARDING_FORK_SLOT)
next_slot(spec, state)

View File

@ -2,12 +2,10 @@ from eth2spec.test.context import (
spec_state_test,
always_bls, never_bls,
with_all_phases,
with_all_phases_except,
spec_test,
low_balances,
with_custom_state,
single_phase,
PHASE1,
)
from eth2spec.test.helpers.attestations import (
run_attestation_processing,
@ -380,7 +378,7 @@ def test_correct_after_epoch_delay(spec, state):
# Incorrect head but correct source/target at different slot inclusions
#
@with_all_phases_except([PHASE1])
@with_all_phases
@spec_state_test
def test_incorrect_head_min_inclusion_delay(spec, state):
attestation = get_valid_attestation(spec, state, signed=False)
@ -434,10 +432,7 @@ def test_incorrect_head_after_epoch_delay(spec, state):
# Incorrect head and target but correct source at different slot inclusions
#
# Note: current phase 1 spec checks
# `assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))`
# so this test can't pass that until phase 1 refactor is merged
@with_all_phases_except([PHASE1])
@with_all_phases
@spec_state_test
def test_incorrect_head_and_target_min_inclusion_delay(spec, state):
attestation = get_valid_attestation(spec, state, signed=False)
@ -494,7 +489,7 @@ def test_incorrect_head_and_target_after_epoch_delay(spec, state):
# Correct head and source but incorrect target at different slot inclusions
#
@with_all_phases_except([PHASE1])
@with_all_phases
@spec_state_test
def test_incorrect_target_min_inclusion_delay(spec, state):
attestation = get_valid_attestation(spec, state, signed=False)

View File

@ -1,7 +1,7 @@
from eth2spec.test.context import (
spec_state_test, spec_test,
with_all_phases, single_phase,
with_phases, PHASE0, PHASE1,
with_phases, PHASE0,
with_custom_state,
zero_activation_threshold,
misc_balances, low_single_balance,
@ -103,7 +103,7 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state):
assert state.balances[index] == pre_state.balances[index]
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_attestations_random_incorrect_fields(spec, state):
attestations = prepare_state_with_attestations(spec, state)
@ -292,8 +292,6 @@ def test_duplicate_attestation(spec, state):
assert single_state.balances[index] == dup_state.balances[index]
# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged
# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_participants_different_attestation_1(spec, state):
@ -334,8 +332,6 @@ def test_duplicate_participants_different_attestation_1(spec, state):
assert single_correct_state.balances[index] == dup_state.balances[index]
# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged
# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_participants_different_attestation_2(spec, state):
@ -377,8 +373,6 @@ def test_duplicate_participants_different_attestation_2(spec, state):
assert single_correct_state.balances[index] == dup_state.balances[index]
# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged
# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_participants_different_attestation_3(spec, state):

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import PHASE0, PHASE1, with_all_phases, with_phases, spec_state_test
from eth2spec.test.context import PHASE0, with_all_phases, with_phases, spec_state_test
import eth2spec.test.helpers.rewards as rewards_helpers
@ -32,7 +32,7 @@ def test_full_but_partial_participation(spec, state):
yield from rewards_helpers.run_test_full_but_partial_participation(spec, state)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_one_attestation_one_correct(spec, state):
yield from rewards_helpers.run_test_one_attestation_one_correct(spec, state)
@ -75,7 +75,7 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state):
#
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_half_correct_target_incorrect_head(spec, state):
yield from rewards_helpers.run_test_full_fraction_incorrect(
@ -86,7 +86,7 @@ def test_full_half_correct_target_incorrect_head(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_correct_target_incorrect_head(spec, state):
yield from rewards_helpers.run_test_full_fraction_incorrect(
@ -97,7 +97,7 @@ def test_full_correct_target_incorrect_head(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_half_incorrect_target_incorrect_head(spec, state):
yield from rewards_helpers.run_test_full_fraction_incorrect(
@ -108,7 +108,7 @@ def test_full_half_incorrect_target_incorrect_head(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_half_incorrect_target_correct_head(spec, state):
yield from rewards_helpers.run_test_full_fraction_incorrect(
@ -119,31 +119,31 @@ def test_full_half_incorrect_target_correct_head(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_delay_one_slot(spec, state):
yield from rewards_helpers.run_test_full_delay_one_slot(spec, state)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_delay_max_slots(spec, state):
yield from rewards_helpers.run_test_full_delay_max_slots(spec, state)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_full_mixed_delay(spec, state):
yield from rewards_helpers.run_test_full_mixed_delay(spec, state)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_proposer_not_in_attestations(spec, state):
yield from rewards_helpers.run_test_proposer_not_in_attestations(spec, state)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_attestations_at_later_slots(spec, state):
yield from rewards_helpers.run_test_duplicate_attestations_at_later_slots(spec, state)

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import PHASE0, PHASE1, with_all_phases, with_phases, spec_state_test
from eth2spec.test.context import PHASE0, with_all_phases, with_phases, spec_state_test
from eth2spec.test.helpers.rewards import leaking
import eth2spec.test.helpers.rewards as rewards_helpers
@ -38,7 +38,7 @@ def test_full_but_partial_participation_leak(spec, state):
yield from rewards_helpers.run_test_full_but_partial_participation(spec, state)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
@leaking()
def test_one_attestation_one_correct_leak(spec, state):
@ -87,7 +87,7 @@ def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state):
#
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
@leaking()
def test_full_half_correct_target_incorrect_head_leak(spec, state):
@ -99,7 +99,7 @@ def test_full_half_correct_target_incorrect_head_leak(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
@leaking()
def test_full_correct_target_incorrect_head_leak(spec, state):
@ -111,7 +111,7 @@ def test_full_correct_target_incorrect_head_leak(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
@leaking()
def test_full_half_incorrect_target_incorrect_head_leak(spec, state):
@ -123,7 +123,7 @@ def test_full_half_incorrect_target_incorrect_head_leak(spec, state):
)
@with_phases([PHASE0, PHASE1])
@with_phases([PHASE0])
@spec_state_test
@leaking()
def test_full_half_incorrect_target_correct_head_leak(spec, state):

View File

@ -20,14 +20,13 @@ from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
from eth2spec.test.helpers.multi_operations import (
run_slash_and_exit,
run_test_full_random_operations,
)
from eth2spec.test.context import (
PHASE0, PHASE1, MINIMAL,
PHASE0, MINIMAL,
spec_test, spec_state_test, dump_skipping_message,
with_phases, with_all_phases, single_phase,
expect_assertion_error, always_bls,
@ -563,7 +562,7 @@ def test_duplicate_attester_slashing(spec, state):
yield 'post', None
# All AttesterSlashing tests should be adopted for Phase 1 but helper support is not yet there
# TODO All AttesterSlashing tests should be adopted for SHARDING and later but helper support is not yet there
@with_all_phases
@spec_state_test
@ -770,16 +769,10 @@ def test_attestation(spec, state):
attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
index = 0
if spec.fork == PHASE1:
shard = spec.compute_shard_from_committee_index(state, index, state.slot)
shard_transition = get_shard_transition_of_committee(spec, state, index)
attestation_block.body.shard_transitions[shard] = shard_transition
else:
shard_transition = None
# if spec.fork == SHARDING:
# TODO add shard data to block to vote on
attestation = get_valid_attestation(
spec, state, shard_transition=shard_transition, index=index, signed=True, on_time=True
)
attestation = get_valid_attestation(spec, state, index=index, signed=True, on_time=True)
if not is_post_altair(spec):
pre_current_attestations_len = len(state.current_epoch_attestations)
@ -810,9 +803,10 @@ def test_attestation(spec, state):
assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root
# In phase1 a committee is computed for SHARD_COMMITTEE_PERIOD slots ago,
# After SHARDING is enabled, a committee is computed for SHARD_COMMITTEE_PERIOD slots ago,
# exceeding the minimal-config randao mixes memory size.
# Applies to all voluntary-exit sanity block tests.
# TODO: when integrating SHARDING tests, voluntary-exit tests may need to change.
@with_all_phases
@spec_state_test

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR, with_all_phases, spec_state_test
from eth2spec.test.context import PHASE0, ALTAIR, with_all_phases, spec_state_test
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation
from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot
@ -23,16 +23,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
)
elif spec.fork == PHASE1:
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
)
shard_latest_message = spec.ShardLatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.shard_head_root,
)
assert store.shard_stores[attestation.data.shard].latest_messages[sample_index] == shard_latest_message
# elif spec.fork == SHARDING: TODO: check if vote count for shard blob increased as expected
assert (
store.latest_messages[sample_index] == latest_message

View File

@ -1,197 +0,0 @@
from eth2spec.test.context import (
PHASE0,
ALTAIR,
with_all_phases_except,
only_full_crosslink,
spec_state_test,
)
from eth2spec.test.helpers.attestations import (
get_valid_attestation,
get_valid_on_time_attestation,
run_attestation_processing,
)
from eth2spec.test.helpers.shard_transitions import (
run_shard_transitions_processing,
)
from eth2spec.test.helpers.shard_block import (
build_shard_block,
get_shard_transitions,
get_sample_shard_block_body,
get_committee_index_of_shard,
)
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot, next_slot
def get_initial_env(spec, state, target_len_offset_slot):
transition_to_valid_shard_slot(spec, state)
committee_index = spec.CommitteeIndex(0)
target_shard_slot = state.slot + target_len_offset_slot - 1
shard = spec.compute_shard_from_committee_index(state, committee_index, target_shard_slot)
assert state.shard_states[shard].slot == state.slot - 1
return state, shard, target_shard_slot
def get_attestations_and_shard_transitions(spec, state, shard_block_dict):
shard_transitions = get_shard_transitions(spec, state, shard_block_dict)
attestations = [
get_valid_on_time_attestation(
spec, state,
index=get_committee_index_of_shard(spec, state, state.slot, shard),
shard_transition=shard_transition,
signed=True,
)
for shard, shard_transition in enumerate(shard_transitions)
if shard_transition != spec.ShardTransition()
]
return attestations, shard_transitions
def run_successful_crosslink_tests(spec, state, target_len_offset_slot):
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot)
init_slot = state.slot
# Create SignedShardBlock at init_slot
shard_block = build_shard_block(
spec, state, shard,
slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
)
# Transition state to target shard slot
transition_to(spec, state, target_shard_slot)
# Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
shard_block_dict = {shard: [shard_block]}
attestations, shard_transitions = get_attestations_and_shard_transitions(spec, state, shard_block_dict)
next_slot(spec, state)
for attestation in attestations:
_, _, _ = run_attestation_processing(spec, state, attestation)
_, winning_roots = spec.get_shard_winning_roots(state, attestations)
assert len(winning_roots) == 1
shard_transition = shard_transitions[shard]
assert winning_roots[0] == shard_transition.hash_tree_root()
pre_gasprice = state.shard_states[shard].gasprice
pre_shard_states = state.shard_states.copy()
yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations)
for index, shard_state in enumerate(state.shard_states):
if index == shard:
assert shard_state != pre_shard_states[index]
assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1]
assert shard_state.latest_block_root == shard_block.message.hash_tree_root()
if target_len_offset_slot == 1:
assert shard_state.gasprice > pre_gasprice
else:
assert shard_state == pre_shard_states[index]
for pending_attestation in state.current_epoch_attestations:
assert bool(pending_attestation.crosslink_success) is True
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_basic_crosslinks(spec, state):
yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_multiple_offset_slots(spec, state):
yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_no_winning_root(spec, state):
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
init_slot = state.slot
# Create SignedShardBlock at init_slot
shard_block = build_shard_block(
spec, state, shard,
slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
)
# Transition state to target shard slot
transition_to(spec, state, target_shard_slot)
# Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]})
shard_transition = shard_transitions[shard]
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
attestation = get_valid_attestation(
spec, state,
index=committee_index,
shard_transition=shard_transition,
# Decrease attested participants to 1/3 committee
filter_participant_set=lambda committee: set(list(committee)[:len(committee) // 3]),
signed=True,
on_time=True,
)
next_slot(spec, state)
_, _, _ = run_attestation_processing(spec, state, attestation)
_, winning_roots = spec.get_shard_winning_roots(state, [attestation])
assert len(winning_roots) == 0
# No winning root, shard_transitions[shard] is empty
shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS
pre_shard_states = state.shard_states.copy()
yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation])
for pending_attestation in state.current_epoch_attestations:
assert bool(pending_attestation.crosslink_success) is False
assert state.shard_states == pre_shard_states
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_wrong_shard_transition_root(spec, state):
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
init_slot = state.slot
# Create SignedShardBlock at init_slot
shard_block = build_shard_block(
spec, state, shard,
slot=init_slot, body=get_sample_shard_block_body(spec, is_max=True), signed=True
)
# Transition state to target shard slot
transition_to(spec, state, target_shard_slot)
# Create a shard_transitions that would be included at beacon block `target_shard_slot + 1`
shard_transitions = get_shard_transitions(spec, state, {shard: [shard_block]})
shard_transition = shard_transitions[shard]
wrong_shard_transition = shard_transition.copy()
wrong_shard_transition.shard_states[shard].gasprice = shard_transition.shard_states[shard].gasprice + 1
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
attestation = get_valid_attestation(
spec, state,
index=committee_index,
shard_transition=wrong_shard_transition,
signed=True,
on_time=True,
)
attestations = [attestation]
next_slot(spec, state)
run_attestation_processing(spec, state, attestation)
# Check if winning root != shard_transition.hash_tree_root()
_, winning_roots = spec.get_shard_winning_roots(state, attestations)
assert len(winning_roots) == 1
shard_transition = shard_transitions[shard]
assert winning_roots[0] != shard_transition.hash_tree_root()
yield from run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=False)

View File

@ -1,251 +0,0 @@
from eth2spec.test.context import (
PHASE0,
ALTAIR,
always_bls,
expect_assertion_error,
spec_state_test,
with_all_phases_except,
only_full_crosslink,
)
from eth2spec.test.helpers.shard_block import (
build_shard_block,
sign_shard_block,
)
from eth2spec.test.helpers.state import next_slot, transition_to_valid_shard_slot, transition_to
def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state, valid=True):
pre_shard_state = shard_state.copy()
yield 'pre', pre_shard_state
yield 'signed_shard_block', signed_shard_block
yield 'beacon_parent_state', beacon_parent_state
if not valid:
expect_assertion_error(
lambda: spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state)
)
yield 'post', None
return
spec.shard_state_transition(shard_state, signed_shard_block, beacon_parent_state)
yield 'post', shard_state
# Verify `process_shard_block`
block = signed_shard_block.message
assert shard_state.slot == block.slot
shard_block_length = len(block.body)
assert shard_state.gasprice == spec.compute_updated_gasprice(pre_shard_state.gasprice, shard_block_length)
if shard_block_length != 0:
shard_state.latest_block_root == block.hash_tree_root()
else:
shard_state.latest_block_root == pre_shard_state.latest_block_root
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_valid_shard_block(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, state, shard, slot=beacon_state.slot, signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state)
#
# verify_shard_block_message
#
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_invalid_shard_parent_root(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block.message.shard_parent_root = b'\x12' * 32
sign_shard_block(spec, beacon_state, shard, signed_shard_block)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_invalid_beacon_parent_root(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block.message.beacon_parent_root = b'\x12' * 32
sign_shard_block(spec, beacon_state, shard, signed_shard_block)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_invalid_slot(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block.message.slot = beacon_state.slot + 1
proposer_index = spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard)
sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_invalid_proposer_index(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
active_validator_indices = spec.get_active_validator_indices(beacon_state, spec.get_current_epoch(beacon_state))
proposer_index = (
(spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + 1)
% len(active_validator_indices)
)
signed_shard_block.message.proposer_index = proposer_index
sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_out_of_bound_offset(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
slot = (
beacon_state.shard_states[shard].slot
+ spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1]
+ 1 # out-of-bound
)
transition_to(spec, beacon_state, slot)
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_invalid_offset(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
# 4 is not in `SHARD_BLOCK_OFFSETS`
shard = 0
slot = beacon_state.shard_states[shard].slot + 4
assert slot not in spec.SHARD_BLOCK_OFFSETS
transition_to(spec, beacon_state, slot)
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_empty_block_body(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, body=b'', signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
#
# verify_shard_block_signature
#
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_invalid_signature(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)
#
# Other cases
#
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_max_offset(spec, state):
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
slot = beacon_state.shard_states[shard].slot + spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1]
transition_to(spec, beacon_state, slot)
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state)
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@always_bls
@only_full_crosslink
def test_pending_shard_parent_block(spec, state):
# Block N
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block_1 = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
_, _, _, _ = run_shard_blocks(spec, shard_state, signed_shard_block_1, beacon_state)
# Block N+1
next_slot(spec, beacon_state)
signed_shard_block_2 = build_shard_block(
spec, beacon_state, shard,
slot=beacon_state.slot, shard_parent_state=shard_state, signed=True
)
assert signed_shard_block_2.message.shard_parent_root == shard_state.latest_block_root
assert signed_shard_block_2.message.slot == signed_shard_block_1.message.slot + 1
yield from run_shard_blocks(spec, shard_state, signed_shard_block_2, beacon_state)

View File

@ -1,280 +0,0 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.context import (
PHASE0,
ALTAIR,
spec_state_test,
with_all_phases_except,
never_bls,
only_full_crosslink,
)
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
from eth2spec.test.helpers.shard_block import (
build_shard_block,
get_shard_transitions,
get_committee_index_of_shard,
)
from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root, get_genesis_forkchoice_store
from eth2spec.test.helpers.state import state_transition_and_sign_block
from eth2spec.test.helpers.block import build_empty_block
def run_on_shard_block(spec, store, signed_block, valid=True):
shard = signed_block.message.shard
if not valid:
try:
spec.on_shard_block(store, signed_block)
except AssertionError:
return
else:
assert False
spec.on_shard_block(store, signed_block)
shard_store = store.shard_stores[shard]
assert shard_store.signed_blocks[hash_tree_root(signed_block.message)] == signed_block
def initialize_store(spec, state, shards):
store = get_genesis_forkchoice_store(spec, state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
for shard in shards:
shard_head_root = spec.get_shard_head(store, shard)
assert shard_head_root == state.shard_states[shard].latest_block_root
shard_store = store.shard_stores[shard]
assert shard_store.block_states[shard_head_root].slot == 0
assert shard_store.block_states[shard_head_root] == state.shard_states[shard]
return store
def create_and_apply_shard_block(spec, store, shard, beacon_parent_state, shard_blocks_buffer):
body = b'\x56' * 4
shard_head_root = spec.get_shard_head(store, shard)
shard_store = store.shard_stores[shard]
shard_parent_state = shard_store.block_states[shard_head_root]
assert shard_parent_state.slot != beacon_parent_state.slot
shard_block = build_shard_block(
spec, beacon_parent_state, shard,
shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True
)
shard_blocks_buffer.append(shard_block)
run_on_shard_block(spec, store, shard_block)
assert spec.get_shard_head(store, shard) == shard_block.message.hash_tree_root()
def check_pending_shard_blocks(spec, store, shard, shard_blocks_buffer):
pending_shard_blocks = spec.get_pending_shard_blocks(store, shard)
assert pending_shard_blocks == shard_blocks_buffer
def is_in_offset_sets(spec, beacon_head_state, shard):
offset_slots = spec.compute_offset_slots(
beacon_head_state.shard_states[shard].slot, beacon_head_state.slot + 1
)
return beacon_head_state.slot in offset_slots
def create_attestation_for_shard_blocks(spec, beacon_parent_state, shard, committee_index, blocks,
filter_participant_set=None):
shard_transition = spec.get_shard_transition(beacon_parent_state, shard, blocks)
attestation = get_valid_on_time_attestation(
spec,
beacon_parent_state,
index=committee_index,
shard_transition=shard_transition,
signed=True,
)
return attestation
def create_beacon_block_with_shard_transition(
spec, state, store, shard, shard_blocks_buffer, is_checking_pending_shard_blocks=True):
beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
has_shard_committee = committee_index is not None # has committee of `shard` at this slot
beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
# If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block
if has_shard_committee and len(shard_blocks_buffer) > 0:
# Sanity check `get_pending_shard_blocks`
# Assert that the pending shard blocks set in the store equal to shard_blocks_buffer
if is_checking_pending_shard_blocks:
check_pending_shard_blocks(spec, store, shard, shard_blocks_buffer)
# Use temporary next state to get ShardTransition of shard block
shard_transitions = get_shard_transitions(spec, state, shard_block_dict={shard: shard_blocks_buffer})
shard_transition = shard_transitions[shard]
attestation = get_valid_on_time_attestation(
spec,
state,
index=committee_index,
shard_transition=shard_transition,
signed=True,
)
assert attestation.data.shard == shard
beacon_block.body.attestations = [attestation]
beacon_block.body.shard_transitions = shard_transitions
# Clear buffer
shard_blocks_buffer.clear()
return beacon_block
def apply_all_attestation_to_store(spec, store, attestations):
for attestation in attestations:
spec.on_attestation(store, attestation)
def apply_beacon_block_to_store(spec, state, store, beacon_block):
signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) # transition!
store.time = store.time + spec.SECONDS_PER_SLOT
add_block_to_store(spec, store, signed_beacon_block)
apply_all_attestation_to_store(spec, store, signed_beacon_block.message.body.attestations)
def create_and_apply_beacon_and_shard_blocks(spec, state, store, shard, shard_blocks_buffer,
is_checking_pending_shard_blocks=True):
beacon_block = create_beacon_block_with_shard_transition(
spec, state, store, shard, shard_blocks_buffer,
is_checking_pending_shard_blocks=is_checking_pending_shard_blocks
)
apply_beacon_block_to_store(spec, state, store, beacon_block)
# On shard block at the transitioned `state.slot`
if is_in_offset_sets(spec, state, shard):
# The created shard block would be appended to `shard_blocks_buffer`
create_and_apply_shard_block(spec, store, shard, state, shard_blocks_buffer)
has_shard_committee = get_committee_index_of_shard(spec, state, state.slot, shard) is not None
return has_shard_committee
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@never_bls # Set to never_bls for testing `check_pending_shard_blocks`
def test_basic(spec, state):
spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here
state = spec.upgrade_to_phase1(state)
shard = spec.Shard(1)
# Initialization
store = initialize_store(spec, state, [shard])
# For mainnet config, it's possible that only one committee of `shard` per epoch.
# we set this counter to test more rounds.
shard_committee_counter = 2
shard_blocks_buffer = [] # the accumulated shard blocks that haven't been crosslinked yet
while shard_committee_counter > 0:
has_shard_committee = create_and_apply_beacon_and_shard_blocks(spec, state, store, shard, shard_blocks_buffer)
if has_shard_committee:
shard_committee_counter -= 1
def create_simple_fork(spec, state, store, shard):
# Beacon block
beacon_block = create_beacon_block_with_shard_transition(spec, state, store, shard, [])
apply_beacon_block_to_store(spec, state, store, beacon_block)
beacon_head_root = spec.get_head(store)
assert beacon_head_root == beacon_block.hash_tree_root()
beacon_parent_state = store.block_states[beacon_head_root]
shard_store = store.shard_stores[shard]
shard_parent_state = shard_store.block_states[spec.get_shard_head(store, shard)]
# Shard block A
body = b'\x56' * 4
forking_block_child = build_shard_block(
spec, beacon_parent_state, shard,
shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True
)
run_on_shard_block(spec, store, forking_block_child)
# Shard block B
body = b'\x78' * 4 # different body
shard_block_b = build_shard_block(
spec, beacon_parent_state, shard,
shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True
)
run_on_shard_block(spec, store, shard_block_b)
# Set forking_block
current_head = spec.get_shard_head(store, shard)
if current_head == forking_block_child.message.hash_tree_root():
head_block = forking_block_child
forking_block = shard_block_b
else:
assert current_head == shard_block_b.message.hash_tree_root()
head_block = shard_block_b
forking_block = forking_block_child
return head_block, forking_block
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_shard_simple_fork(spec, state):
spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here
state = spec.upgrade_to_phase1(state)
shard = spec.Shard(1)
# Initialization
store = initialize_store(spec, state, [shard])
# Create fork
_, forking_block = create_simple_fork(spec, state, store, shard)
# Vote for forking_block
state = store.block_states[spec.get_head(store)].copy()
beacon_block = create_beacon_block_with_shard_transition(spec, state, store, shard, [forking_block],
is_checking_pending_shard_blocks=False)
store.time = store.time + spec.SECONDS_PER_SLOT
apply_all_attestation_to_store(spec, store, beacon_block.body.attestations)
# Head block has been changed
assert spec.get_shard_head(store, shard) == forking_block.message.hash_tree_root()
@with_all_phases_except([PHASE0, ALTAIR])
@spec_state_test
@only_full_crosslink
def test_shard_latest_messages_for_different_shards(spec, state):
spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here
state = spec.upgrade_to_phase1(state)
shard_0 = spec.Shard(0)
shard_1 = spec.Shard(1)
# Initialization
store = initialize_store(spec, state, [shard_0, shard_1])
# Shard 0 ----------------------------------
# Create fork on shard 0
_, forking_block = create_simple_fork(spec, state, store, shard_0)
# Vote for forking_block on shard 0
state = store.block_states[spec.get_head(store)].copy()
beacon_block = create_beacon_block_with_shard_transition(spec, state, store, shard_0, [forking_block],
is_checking_pending_shard_blocks=False)
store.time = store.time + spec.SECONDS_PER_SLOT
apply_all_attestation_to_store(spec, store, beacon_block.body.attestations)
# Head block of shard 0 has been changed due to the shard latest messages
assert spec.get_shard_head(store, shard_0) == forking_block.message.hash_tree_root()
# Shard 1 ----------------------------------
# Run shard 1 after 1~2 epochs
shard_committee_counter = 2
shard_blocks_buffer = [] # the accumulated shard blocks that haven't been crosslinked yet
while shard_committee_counter > 0:
has_shard_committee = create_and_apply_beacon_and_shard_blocks(
spec, state, store, shard_1, shard_blocks_buffer
)
if has_shard_committee:
shard_committee_counter -= 1
# Go back to see shard 0 ----------------------------------
# The head block of shard 0 should be unchanged.
assert spec.get_shard_head(store, shard_0) == forking_block.message.hash_tree_root()

View File

@ -1,13 +1,12 @@
from eth2spec.test.context import (
PHASE0,
ALTAIR,
with_all_phases_except,
SHARDING,
with_phases,
spec_state_test,
)
from eth2spec.test.helpers.state import next_epoch
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([SHARDING])
@spec_state_test
def test_get_committee_count_delta(spec, state):
assert spec.get_committee_count_delta(state, 0, 0) == 0
@ -24,7 +23,7 @@ def test_get_committee_count_delta(spec, state):
)
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([SHARDING])
@spec_state_test
def test_get_start_shard_current_epoch_start(spec, state):
assert state.current_epoch_start_shard == 0
@ -40,7 +39,7 @@ def test_get_start_shard_current_epoch_start(spec, state):
assert start_shard == state.current_epoch_start_shard
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([SHARDING])
@spec_state_test
def test_get_start_shard_next_slot(spec, state):
next_epoch(spec, state)
@ -58,7 +57,7 @@ def test_get_start_shard_next_slot(spec, state):
assert start_shard == expected_start_shard
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([SHARDING])
@spec_state_test
def test_get_start_shard_previous_slot(spec, state):
next_epoch(spec, state)
@ -77,7 +76,7 @@ def test_get_start_shard_previous_slot(spec, state):
assert start_shard == expected_start_shard
@with_all_phases_except([PHASE0, ALTAIR])
@with_phases([SHARDING])
@spec_state_test
def test_get_start_shard_far_past_epoch(spec, state):
initial_epoch = spec.get_current_epoch(state)

View File

@ -108,8 +108,8 @@ As a top level dir, it is not duplicated, and the used config can be copied righ
### `<fork or phase name>/`
This would be: "phase0", "transferparty", "phase1", etc. Each introduces new tests, but does not copy tests that do not change.
If you like to test phase 1, you run phase 0 tests, with the configuration that includes phase 1 changes. Out of scope for now however.
This would be: "phase0", "altair", etc. Each introduces new tests, and modifies any tests that change:
some tests of earlier forks repeat with updated state data.
### `<test runner name>/`

View File

@ -164,13 +164,12 @@ Another example, to generate tests from pytests:
```python
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
@ -181,15 +180,10 @@ if __name__ == "__main__":
altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [
'blocks',
]}, **phase_0_mods} # also run the previous phase 0 tests
phase_1_mods = {**{key: 'eth2spec.test.phase1.sanity.test_' + key for key in [
'blocks', # more phase 1 specific block tests
'shard_blocks',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="sanity", specs=specs, all_mods=all_mods)

View File

@ -1,11 +1,10 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
@ -27,16 +26,17 @@ if __name__ == "__main__":
]},
**phase_0_mods,
} # also run the previous phase 0 tests
phase_1_mods = {**{key: 'eth2spec.test.phase1.epoch_processing.test_process_' + key for key in [
'reveal_deadlines',
'challenge_deadlines',
'custody_final_updates',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
# TODO Custody Game testgen is disabled for now
# custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [
# 'reveal_deadlines',
# 'challenge_deadlines',
# 'custody_final_updates',
# ]}, **phase_0_mods} # also run the previous phase 0 tests (but against custody game spec)
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="epoch_processing", specs=specs, all_mods=all_mods)

View File

@ -1,23 +1,19 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
phase_0_mods = {'finality': 'eth2spec.test.phase0.finality.test_finality'}
# No additional altair or phase 1 specific finality tests, yet.
altair_mods = phase_0_mods
phase_1_mods = phase_0_mods
altair_mods = phase_0_mods # No additional altair specific finality tests
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="finality", specs=specs, all_mods=all_mods)

View File

@ -1,25 +1,22 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [
'get_head',
]}
# No additional Altair or phase 1 specific finality tests, yet.
# No additional Altair specific finality tests, yet.
altair_mods = phase_0_mods
phase_1_mods = phase_0_mods
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="fork_choice", specs=specs, all_mods=all_mods)

View File

@ -1,11 +1,10 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
@ -14,11 +13,9 @@ if __name__ == "__main__":
'validity',
]}
altair_mods = phase_0_mods
phase_1_mods = phase_0_mods
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="genesis", specs=specs, all_mods=all_mods)

View File

@ -1,11 +1,10 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
@ -23,19 +22,19 @@ if __name__ == "__main__":
]},
**phase_0_mods,
} # also run the previous phase 0 tests
phase_1_mods = {**{key: 'eth2spec.test.phase1.block_processing.test_process_' + key for key in [
'attestation',
'chunk_challenge',
'custody_key_reveal',
'custody_slashing',
'early_derived_secret_reveal',
'shard_transition',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
# TODO Custody Game testgen is disabled for now
# custody_game_mods = {**{key: 'eth2spec.test.custody_game.block_processing.test_process_' + key for key in [
# 'attestation',
# 'chunk_challenge',
# 'custody_key_reveal',
# 'custody_slashing',
# 'early_derived_secret_reveal',
# ]}, **phase_0_mods} # also run the previous phase 0 tests (but against custody game spec)
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="operations", specs=specs, all_mods=all_mods)

View File

@ -1,11 +1,10 @@
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
@ -14,14 +13,12 @@ if __name__ == "__main__":
'leak',
'random',
]}
# No additional altair or phase 1 specific rewards tests, yet.
# No additional altair specific rewards tests, yet.
altair_mods = phase_0_mods
phase_1_mods = phase_0_mods
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="rewards", specs=specs, all_mods=all_mods)

View File

@ -1,12 +1,11 @@
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.altair import spec as spec_altair
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1, ALTAIR
from eth2spec.test.context import PHASE0, ALTAIR
from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators
specs = (spec_phase0, spec_altair, spec_phase1)
specs = (spec_phase0, spec_altair)
if __name__ == "__main__":
@ -17,15 +16,10 @@ if __name__ == "__main__":
altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [
'blocks',
]}, **phase_0_mods} # also run the previous phase 0 tests
phase_1_mods = {**{key: 'eth2spec.test.phase1.sanity.test_' + key for key in [
'blocks', # more phase 1 specific block tests
'shard_blocks',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
all_mods = {
PHASE0: phase_0_mods,
ALTAIR: altair_mods,
PHASE1: phase_1_mods,
}
run_state_test_generators(runner_name="sanity", specs=specs, all_mods=all_mods)

View File

@ -8,9 +8,8 @@ from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing
from eth2spec.debug import random_value, encode
from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.altair import spec as spec_altair
from eth2spec.test.context import PHASE1, ALTAIR, TESTGEN_FORKS, MINIMAL, MAINNET
from eth2spec.test.context import ALTAIR, TESTGEN_FORKS, MINIMAL, MAINNET
from eth2spec.utils.ssz.ssz_typing import Container
from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root,
@ -64,15 +63,12 @@ def create_provider(fork_name, config_name: str, seed: int, mode: random_value.R
# Apply changes to presets, this affects some of the vector types.
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
reload(spec_phase1)
reload(spec_altair)
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
count = cases_if_random if chaos or mode.is_changing() else 1
spec = spec_phase0
if fork_name == PHASE1:
spec = spec_phase1
if fork_name == ALTAIR:
spec = spec_altair