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:
commit
51db49988a
|
@ -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
|
||||
|
|
14
Makefile
14
Makefile
|
@ -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); \
|
||||
|
|
37
README.md
37
README.md
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
62
setup.py
62
setup.py
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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)
|
||||
```
|
|
@ -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)
|
||||
```
|
|
@ -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`.
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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
|
||||
```
|
|
@ -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
|
||||
```
|
|
@ -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.
|
|
@ -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
|
||||
```
|
|
@ -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
|
||||
...
|
||||
```
|
|
@ -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.
|
|
@ -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))
|
||||
```
|
|
@ -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.
|
||||
|
|
@ -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}'")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
|
@ -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")
|
|
@ -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):
|
|
@ -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")
|
|
@ -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):
|
|
@ -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):
|
|
@ -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)
|
|
@ -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):
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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)
|
|
@ -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>/`
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue