Merge branch 'dev' into testgenphase1

This commit is contained in:
Hsiao-Wei Wang 2020-08-11 15:24:43 +08:00
commit 3e5cada5de
No known key found for this signature in database
GPG Key ID: 95B070122902DEA4
60 changed files with 929 additions and 477 deletions

View File

@ -86,7 +86,7 @@ find_test: pyspec
citest: pyspec
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
python -m pytest -n 4 --disable-bls --junitxml=eth2spec/test_results.xml eth2spec
python -m pytest -n 4 --bls-type=milagro --junitxml=eth2spec/test_results.xml eth2spec
open_cov:
((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) &

View File

@ -2,7 +2,7 @@
[![Join the chat at https://discord.gg/hpFs23p](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/hpFs23p) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQ) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm).
To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm).
This repository hosts the current Eth2 specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests.

View File

@ -15,7 +15,7 @@ Over time, the need to sync an older state may be deprecated.
In this case, the prefix on the new constant may be removed, and the old constant will keep a special name before completely being removed.
A previous iteration of forking made use of "timelines", but this collides with the definitions used in the spec (constants for special forking slots, etc.), and was not integrated sufficiently in any of the spec tools or implementations.
Instead, the config essentially doubles as fork definition now, e.g. changing the value for `PHASE_1_GENESIS_SLOT` changes the fork.
Instead, the config essentially doubles as fork definition now, e.g. changing the value for `PHASE_1_FORK_SLOT` changes the fork.
Another reason to prefer forking through constants is the ability to program a forking moment based on context, instead of being limited to a static slot number.

View File

@ -51,6 +51,9 @@ SECONDS_PER_ETH1_BLOCK: 14
# Deposit contract
# ---------------------------------------------------------------
# Ethereum PoW Mainnet
DEPOSIT_CHAIN_ID: 1
DEPOSIT_NETWORK_ID: 1
# **TBD**
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890

View File

@ -5,7 +5,7 @@
# ---------------------------------------------------------------
PHASE_1_FORK_VERSION: 0x01000000
# [STUB]
PHASE_1_GENESIS_SLOT: 32
PHASE_1_FORK_SLOT: 0
INITIAL_ACTIVE_SHARDS: 64
@ -69,8 +69,6 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768
EPOCHS_PER_CUSTODY_PERIOD: 16384
# 2**11 (= 2,048) epochs, ~9 days
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**14 (= 16,384) epochs
CUSTODY_RESPONSE_DEADLINE: 16384
# 2**15 (= 32,768) epochs, ~146 days
MAX_CHUNK_CHALLENGE_DELAY: 32768

View File

@ -51,6 +51,9 @@ SECONDS_PER_ETH1_BLOCK: 14
# Deposit contract
# ---------------------------------------------------------------
# Ethereum Goerli testnet
DEPOSIT_CHAIN_ID: 5
DEPOSIT_NETWORK_ID: 5
# **TBD**
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890

View File

@ -5,8 +5,8 @@
# ---------------------------------------------------------------
# [customized] for testnet distinction
PHASE_1_FORK_VERSION: 0x01000001
# [customized] for testing
PHASE_1_GENESIS_SLOT: 8
# [STUB]
PHASE_1_FORK_SLOT: 0
# [customized] reduced for testing
INITIAL_ACTIVE_SHARDS: 2
@ -66,15 +66,13 @@ DOMAIN_LIGHT_AGGREGATE_AND_PROOF: 0x85000000
# 2**1 (= 2) epochs
RANDAO_PENALTY_EPOCHS: 2
# [customized] quicker for testing
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 128
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 64
# [customized] quicker for testing
EPOCHS_PER_CUSTODY_PERIOD: 64
EPOCHS_PER_CUSTODY_PERIOD: 32
# [customized] quicker for testing
CUSTODY_PERIOD_TO_RANDAO_PADDING: 8
# [customized] quicker for testing
CUSTODY_RESPONSE_DEADLINE: 128
# [customize for faster testing]
MAX_CHUNK_CHALLENGE_DELAY: 128
MAX_CHUNK_CHALLENGE_DELAY: 64
# Misc parameters

View File

@ -1,3 +1,4 @@
from enum import Enum, auto
from setuptools import setup, find_packages, Command
from setuptools.command.build_py import build_py
from distutils import dir_util
@ -14,6 +15,13 @@ class SpecObject(NamedTuple):
custom_types: Dict[str, str]
constants: Dict[str, str]
ssz_objects: Dict[str, str]
dataclasses: Dict[str, str]
class CodeBlockType(Enum):
SSZ = auto()
DATACLASS = auto()
FUNCTION = auto()
def get_spec(file_name: str) -> SpecObject:
@ -28,8 +36,9 @@ def get_spec(file_name: str) -> SpecObject:
functions: Dict[str, str] = {}
constants: Dict[str, str] = {}
ssz_objects: Dict[str, str] = {}
dataclasses: Dict[str, str] = {}
function_matcher = re.compile(FUNCTION_REGEX)
is_ssz = False
block_type = CodeBlockType.FUNCTION
custom_types: Dict[str, str] = {}
for linenum, line in enumerate(open(file_name).readlines()):
line = line.rstrip()
@ -43,20 +52,26 @@ def get_spec(file_name: str) -> SpecObject:
else:
# Handle function definitions & ssz_objects
if pulling_from is not None:
# SSZ Object
if len(line) > 18 and line[:6] == 'class ' and line[-12:] == '(Container):':
name = line[6:-12]
# Check consistency with markdown header
assert name == current_name
is_ssz = True
# function definition
block_type = CodeBlockType.SSZ
elif line[:10] == '@dataclass':
block_type = CodeBlockType.DATACLASS
elif function_matcher.match(line) is not None:
current_name = function_matcher.match(line).group(0)
is_ssz = False
if is_ssz:
block_type = CodeBlockType.FUNCTION
if block_type == CodeBlockType.SSZ:
ssz_objects[current_name] = ssz_objects.get(current_name, '') + line + '\n'
else:
elif block_type == CodeBlockType.DATACLASS:
dataclasses[current_name] = dataclasses.get(current_name, '') + line + '\n'
elif block_type == CodeBlockType.FUNCTION:
functions[current_name] = functions.get(current_name, '') + line + '\n'
else:
pass
# Handle constant and custom types table entries
elif pulling_from is None and len(line) > 0 and line[0] == '|':
row = line[1:].split('|')
@ -75,7 +90,7 @@ def get_spec(file_name: str) -> SpecObject:
constants[row[0]] = row[1].replace('**TBD**', '2**32')
elif row[1].startswith('uint') or row[1].startswith('Bytes'):
custom_types[row[0]] = row[1]
return SpecObject(functions, custom_types, constants, ssz_objects)
return SpecObject(functions, custom_types, constants, ssz_objects, dataclasses)
CONFIG_LOADER = '''
@ -94,9 +109,9 @@ from dataclasses import (
from lru import LRU
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
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, uint64,
View, boolean, Container, List, Vector, uint8, uint32, uint64,
Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils import bls
@ -118,9 +133,9 @@ from dataclasses import (
from lru import LRU
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
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, uint64, uint8, bit,
View, boolean, Container, List, Vector, uint8, uint32, uint64, bit,
ByteList, ByteVector, Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils import bls
@ -141,11 +156,6 @@ def ceillog2(x: uint64) -> int:
return (x - 1).bit_length()
'''
PHASE0_SUNDRY_FUNCTIONS = '''
# Monkey patch hash cache
_hash = hash
hash_cache: Dict[bytes, Bytes32] = {}
def get_eth1_data(block: Eth1Block) -> Eth1Data:
"""
A stub function return mocking Eth1Data.
@ -156,12 +166,6 @@ def get_eth1_data(block: Eth1Block) -> Eth1Data:
block_hash=hash_tree_root(block))
def hash(x: bytes) -> Bytes32: # type: ignore
if x not in hash_cache:
hash_cache[x] = Bytes32(_hash(x))
return hash_cache[x]
def cache_this(key_fn, value_fn, lru_size): # type: ignore
cache_dict = LRU(size=lru_size)
@ -225,19 +229,13 @@ get_attesting_indices = cache_this(
PHASE1_SUNDRY_FUNCTIONS = '''
def get_block_data_merkle_root(data: ByteList) -> Root:
# To get the Merkle root of the block data, we need the Merkle root without the length mix-in
# The below implements this in the Remerkleable framework
return data.get_backing().get_left().merkle_root()
_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)'''
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
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.
"""
@ -257,7 +255,7 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
if k == "BLS12_381_Q":
spec_object.constants[k] += " # noqa: E501"
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, spec_object.constants[x]), spec_object.constants))
ssz_objects_instantiation_spec = '\n\n'.join(spec_object.ssz_objects.values())
ordered_class_objects_spec = '\n\n'.join(ordered_class_objects.values())
spec = (
imports
+ '\n\n' + f"fork = \'{fork}\'\n"
@ -265,7 +263,7 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
+ '\n' + SUNDRY_CONSTANTS_FUNCTIONS
+ '\n\n' + constants_spec
+ '\n\n' + CONFIG_LOADER
+ '\n\n' + ssz_objects_instantiation_spec
+ '\n\n' + ordered_class_objects_spec
+ '\n\n' + functions_spec
+ '\n' + PHASE0_SUNDRY_FUNCTIONS
)
@ -291,11 +289,12 @@ ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature',
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector'
'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field',
]
def dependency_order_ssz_objects(objects: Dict[str, str], custom_types: Dict[str, str]) -> None:
def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[str, str]) -> None:
"""
Determines which SSZ Object is dependent on which other and orders them appropriately
"""
@ -332,13 +331,14 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject:
"""
Takes in two spec variants (as tuples of their objects) and combines them using the appropriate combiner function.
"""
functions0, custom_types0, constants0, ssz_objects0 = spec0
functions1, custom_types1, constants1, ssz_objects1 = spec1
functions0, custom_types0, constants0, ssz_objects0, dataclasses0 = spec0
functions1, custom_types1, constants1, ssz_objects1, dataclasses1 = spec1
functions = combine_functions(functions0, functions1)
custom_types = combine_constants(custom_types0, custom_types1)
constants = combine_constants(constants0, constants1)
ssz_objects = combine_ssz_objects(ssz_objects0, ssz_objects1, custom_types)
return SpecObject(functions, custom_types, constants, ssz_objects)
dataclasses = combine_functions(dataclasses0, dataclasses1)
return SpecObject(functions, custom_types, constants, ssz_objects, dataclasses)
fork_imports = {
@ -354,9 +354,10 @@ def build_spec(fork: str, source_files: List[str]) -> str:
for value in all_specs[1:]:
spec_object = combine_spec_objects(spec_object, value)
dependency_order_ssz_objects(spec_object.ssz_objects, spec_object.custom_types)
class_objects = {**spec_object.ssz_objects, **spec_object.dataclasses}
dependency_order_class_objects(class_objects, spec_object.custom_types)
return objects_to_spec(spec_object, fork_imports[fork], fork)
return objects_to_spec(spec_object, fork_imports[fork], fork, class_objects)
class PySpecCommand(Command):
@ -529,7 +530,7 @@ setup(
"py_ecc==4.0.0",
"milagro_bls_binding==1.3.0",
"dataclasses==0.6",
"remerkleable==0.1.16",
"remerkleable==0.1.17",
"ruamel.yaml==0.16.5",
"lru-dict==1.1.6"
]

View File

@ -55,8 +55,8 @@
- [Math](#math)
- [`integer_squareroot`](#integer_squareroot)
- [`xor`](#xor)
- [`int_to_bytes`](#int_to_bytes)
- [`bytes_to_int`](#bytes_to_int)
- [`uint_to_bytes`](#uint_to_bytes)
- [`bytes_to_uint64`](#bytes_to_uint64)
- [Crypto](#crypto)
- [`hash`](#hash)
- [`hash_tree_root`](#hash_tree_root)
@ -170,9 +170,9 @@ The following values are (non-configurable) constants used throughout the specif
| `GENESIS_SLOT` | `Slot(0)` |
| `GENESIS_EPOCH` | `Epoch(0)` |
| `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` |
| `BASE_REWARDS_PER_EPOCH` | `4` |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
| `JUSTIFICATION_BITS_LENGTH` | `4` |
| `BASE_REWARDS_PER_EPOCH` | `uint64(4)` |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `uint64(2**5)` (= 32) |
| `JUSTIFICATION_BITS_LENGTH` | `uint64(4)` |
| `ENDIANNESS` | `'little'` |
## Configuration
@ -183,18 +183,18 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value |
| - | - |
| `ETH1_FOLLOW_DISTANCE` | `2**10` (= 1,024) |
| `MAX_COMMITTEES_PER_SLOT` | `2**6` (= 64) |
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
| `MAX_VALIDATORS_PER_COMMITTEE` | `2**11` (= 2,048) |
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
| `SHUFFLE_ROUND_COUNT` | `90` |
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**14` (= 16,384) |
| `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) |
| `HYSTERESIS_QUOTIENT` | `4` |
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `1` |
| `HYSTERESIS_UPWARD_MULTIPLIER` | `5` |
| `ETH1_FOLLOW_DISTANCE` | `uint64(2**10)` (= 1,024) |
| `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) |
| `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) |
| `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) |
| `MIN_PER_EPOCH_CHURN_LIMIT` | `uint64(2**2)` (= 4) |
| `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) |
| `SHUFFLE_ROUND_COUNT` | `uint64(90)` |
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) |
| `MIN_GENESIS_TIME` | `uint64(1578009600)` (Jan 3, 2020) |
| `HYSTERESIS_QUOTIENT` | `uint64(4)` |
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` |
| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` |
- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
@ -218,37 +218,37 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `GENESIS_DELAY` | `172800` | seconds | 2 days |
| `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds |
| `SECONDS_PER_ETH1_BLOCK` | `14` | seconds | 14 seconds |
| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds |
| `SLOTS_PER_EPOCH` | `2**5` (= 32) | slots | 6.4 minutes |
| `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes |
| `MAX_SEED_LOOKAHEAD` | `2**2` (= 4) | epochs | 25.6 minutes |
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes |
| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `2**5` (= 32) | epochs | ~3.4 hours |
| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours |
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
| `SHARD_COMMITTEE_PERIOD` | `Epoch(2**8)` (= 256) | epochs | ~27 hours |
| `GENESIS_DELAY` | `uint64(172800)` | seconds | 2 days |
| `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds |
| `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds |
| `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds |
| `SLOTS_PER_EPOCH` | `uint64(2**5)` (= 32) | slots | 6.4 minutes |
| `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes |
| `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes |
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes |
| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**5)` (= 32) | epochs | ~3.4 hours |
| `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours |
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
| `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
### State list lengths
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `EPOCHS_PER_HISTORICAL_VECTOR` | `2**16` (= 65,536) | epochs | ~0.8 years |
| `EPOCHS_PER_SLASHINGS_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days |
| `HISTORICAL_ROOTS_LIMIT` | `2**24` (= 16,777,216) | historical roots | ~52,262 years |
| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validators |
| `EPOCHS_PER_HISTORICAL_VECTOR` | `uint64(2**16)` (= 65,536) | epochs | ~0.8 years |
| `EPOCHS_PER_SLASHINGS_VECTOR` | `uint64(2**13)` (= 8,192) | epochs | ~36 days |
| `HISTORICAL_ROOTS_LIMIT` | `uint64(2**24)` (= 16,777,216) | historical roots | ~52,262 years |
| `VALIDATOR_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | validators |
### Rewards and penalties
| Name | Value |
| - | - |
| `BASE_REWARD_FACTOR` | `2**6` (= 64) |
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `2**9` (= 512) |
| `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) |
| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) |
| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) |
| `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) |
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) |
| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) |
| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**24)` (= 16,777,216) |
| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**5)` (= 32) |
- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12` epochs (about 18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`.
@ -576,24 +576,18 @@ def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32:
return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2))
```
#### `int_to_bytes`
#### `uint_to_bytes`
`def uint_to_bytes(n: uint) -> bytes` is a function for serializing the `uint` type object to bytes in ``ENDIANNESS``-endian. The expected length of the output is the byte-length of the `uint` type.
#### `bytes_to_uint64`
```python
def int_to_bytes(n: uint64, length: uint64) -> bytes:
"""
Return the ``length``-byte serialization of ``n`` in ``ENDIANNESS``-endian.
"""
return n.to_bytes(length, ENDIANNESS)
```
#### `bytes_to_int`
```python
def bytes_to_int(data: bytes) -> uint64:
def bytes_to_uint64(data: bytes) -> uint64:
"""
Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian.
"""
return int.from_bytes(data, ENDIANNESS)
return uint64(int.from_bytes(data, ENDIANNESS))
```
### Crypto
@ -733,11 +727,15 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) ->
# Swap or not (https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf)
# See the 'generalized domain' algorithm on page 3
for current_round in range(SHUFFLE_ROUND_COUNT):
pivot = bytes_to_int(hash(seed + int_to_bytes(current_round, length=1))[0:8]) % index_count
pivot = bytes_to_uint64(hash(seed + uint_to_bytes(uint8(current_round)))[0:8]) % index_count
flip = (pivot + index_count - index) % index_count
position = max(index, flip)
source = hash(seed + int_to_bytes(current_round, length=1) + int_to_bytes(position // 256, length=4))
byte = source[(position % 256) // 8]
source = hash(
seed
+ uint_to_bytes(uint8(current_round))
+ uint_to_bytes(uint32(position // 256))
)
byte = uint8(source[(position % 256) // 8])
bit = (byte >> (position % 8)) % 2
index = flip if bit else index
@ -753,10 +751,11 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex]
"""
assert len(indices) > 0
MAX_RANDOM_BYTE = 2**8 - 1
i = 0
i = uint64(0)
total = uint64(len(indices))
while True:
candidate_index = indices[compute_shuffled_index(i % len(indices), len(indices), seed)]
random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32]
candidate_index = indices[compute_shuffled_index(i % total, total, seed)]
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
effective_balance = state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
return candidate_index
@ -775,7 +774,7 @@ def compute_committee(indices: Sequence[ValidatorIndex],
"""
start = (len(indices) * index) // count
end = (len(indices) * (index + 1)) // count
return [indices[compute_shuffled_index(i, len(indices), seed)] for i in range(start, end)]
return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)]
```
#### `compute_epoch_at_slot`
@ -934,7 +933,7 @@ def get_validator_churn_limit(state: BeaconState) -> uint64:
Return the validator churn limit for the current epoch.
"""
active_validator_indices = get_active_validator_indices(state, get_current_epoch(state))
return max(MIN_PER_EPOCH_CHURN_LIMIT, len(active_validator_indices) // CHURN_LIMIT_QUOTIENT)
return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT)
```
#### `get_seed`
@ -945,7 +944,7 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes
Return the seed at ``epoch``.
"""
mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow
return hash(domain_type + int_to_bytes(epoch, length=8) + mix)
return hash(domain_type + uint_to_bytes(epoch) + mix)
```
#### `get_committee_count_per_slot`
@ -955,9 +954,9 @@ 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(1, min(
return max(uint64(1), min(
MAX_COMMITTEES_PER_SLOT,
len(get_active_validator_indices(state, epoch)) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
))
```
@ -986,7 +985,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
Return the beacon proposer index at the current slot.
"""
epoch = get_current_epoch(state)
seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + int_to_bytes(state.slot, length=8))
seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot))
indices = get_active_validator_indices(state, epoch)
return compute_proposer_index(state, indices, seed)
```
@ -1306,6 +1305,8 @@ def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAtte
```python
def process_justification_and_finalization(state: BeaconState) -> None:
# Initial FFG checkpoint values have a `0x00` stub for `root`.
# Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub.
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
return
@ -1316,7 +1317,7 @@ def process_justification_and_finalization(state: BeaconState) -> None:
# Process justifications
state.previous_justified_checkpoint = state.current_justified_checkpoint
state.justification_bits[1:] = state.justification_bits[:-1]
state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1]
state.justification_bits[0] = 0b0
matching_target_attestations = get_matching_target_attestations(state, previous_epoch) # Previous epoch
if get_attesting_balance(state, matching_target_attestations) * 3 >= get_total_active_balance(state) * 2:
@ -1513,6 +1514,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
```python
def process_rewards_and_penalties(state: BeaconState) -> None:
# No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch
if get_current_epoch(state) == GENESIS_EPOCH:
return
@ -1719,10 +1721,10 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla
```python
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.index < get_committee_count_per_slot(state, data.target.epoch)
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
assert data.target.epoch == compute_epoch_at_slot(data.slot)
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH
assert data.index < get_committee_count_per_slot(state, data.target.epoch)
committee = get_beacon_committee(state, data.slot, data.index)
assert len(attestation.aggregation_bits) == len(committee)

View File

@ -10,7 +10,7 @@
- [Introduction](#introduction)
- [Constants](#constants)
- [Contract](#contract)
- [Configuration](#configuration)
- [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract)
- [`deposit` function](#deposit-function)
- [Deposit amount](#deposit-amount)
@ -27,16 +27,29 @@ This document represents the specification for the beacon chain deposit contract
## Constants
### Contract
The following values are (non-configurable) constants used throughout the specification.
| Name | Value |
| - | - |
| `DEPOSIT_CONTRACT_ADDRESS` | **TBD** |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
## Configuration
*Note*: The default mainnet configuration values are included here for spec-design purposes.
The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory.
These configurations are updated for releases and may be out of sync during `dev` changes.
| Name | Value |
| - | - |
| `DEPOSIT_CHAIN_ID` | `1` |
| `DEPOSIT_NETWORK_ID` | `1` |
| `DEPOSIT_CONTRACT_ADDRESS` | **TBD** |
## Ethereum 1.0 deposit contract
The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to Ethereum 1.0 for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2.
The initial deployment phases of Ethereum 2.0 are implemented without consensus changes to Ethereum 1.0. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum 1.0 chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the shards in Phase 2.
_Note_: See [here](https://chainid.network/) for a comprehensive list of public Ethereum chain chain-id's and network-id's.
### `deposit` function

View File

@ -102,7 +102,7 @@ _The block for `anchor_root` is incorrectly initialized to the block header, rat
```python
def get_forkchoice_store(anchor_state: BeaconState) -> Store:
anchor_block_header = anchor_state.latest_block_header.copy()
anchor_block_header = copy(anchor_state.latest_block_header)
if anchor_block_header.state_root == Bytes32():
anchor_block_header.state_root = hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block_header)
@ -110,14 +110,14 @@ def get_forkchoice_store(anchor_state: BeaconState) -> Store:
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,
time=uint64(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: anchor_block_header},
block_states={anchor_root: anchor_state.copy()},
checkpoint_states={justified_checkpoint: anchor_state.copy()},
block_states={anchor_root: copy(anchor_state)},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
)
```
@ -302,7 +302,7 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None:
def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None:
# Store target checkpoint state if not yet seen
if target not in store.checkpoint_states:
base_state = store.block_states[target.root].copy()
base_state = copy(store.block_states[target.root])
if base_state.slot < compute_start_slot_at_epoch(target.epoch):
process_slots(base_state, compute_start_slot_at_epoch(target.epoch))
store.checkpoint_states[target] = base_state
@ -348,7 +348,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
# Parent block must be known
assert block.parent_root in store.block_states
# Make a copy of the state to avoid mutability issues
pre_state = store.block_states[block.parent_root].copy()
pre_state = copy(store.block_states[block.parent_root])
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
assert get_current_slot(store) >= block.slot

View File

@ -41,7 +41,7 @@ It consists of four main sections:
- [Requesting side](#requesting-side)
- [Responding side](#responding-side)
- [Encoding strategies](#encoding-strategies)
- [SSZ-encoding strategy (with or without Snappy)](#ssz-encoding-strategy-with-or-without-snappy)
- [SSZ-snappy encoding strategy](#ssz-snappy-encoding-strategy)
- [Messages](#messages)
- [Status](#status)
- [Goodbye](#goodbye)
@ -79,6 +79,7 @@ It consists of four main sections:
- [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair)
- [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes)
- [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id)
- [Why are these specific gossip parameters chosen?](#why-are-these-specific-gossip-parameters-chosen)
- [Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?](#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets)
- [Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets?](#why-are-there-attestation_subnet_count-attestation-subnets)
- [Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots?](#why-are-attestations-limited-to-be-broadcast-on-gossip-channels-within-slots_per_epoch-slots)
@ -201,7 +202,7 @@ and will in most cases be out of sync with the ENR sequence number.
## The gossip domain: gossipsub
Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) libp2p Protocol
Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md) libp2p Protocol
including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) extension.
**Protocol ID:** `/meshsub/1.1.0`
@ -210,16 +211,17 @@ including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsu
*Note*: Parameters listed here are subject to a large-scale network feasibility study.
The following gossipsub [parameters](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub#meshsub-an-overlay-mesh-router) will be used:
The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used:
- `D` (topic stable mesh target count): 6
- `D_low` (topic stable mesh low watermark): 4
- `D_low` (topic stable mesh low watermark): 5
- `D_high` (topic stable mesh high watermark): 12
- `D_lazy` (gossip target): 6
- `heartbeat_interval` (frequency of heartbeat, seconds): 0.7
- `fanout_ttl` (ttl for fanout maps for topics we are not subscribed to but have published to, seconds): 60
- `gossip_advertise` (number of windows to gossip about): 3
- `gossip_history` (number of heartbeat intervals to retain message IDs): 5
- `heartbeat_interval` (frequency of heartbeat, seconds): 1
- `mcache_len` (number of windows to retain full messages in cache for `IWANT` responses): 6
- `mcache_gossip` (number of windows to gossip about): 3
- `seen_ttl` (number of heartbeat intervals to retain message IDs): 550
### Topics and messages
@ -263,6 +265,8 @@ Clients MUST reject (fail validation) messages containing an incorrect type, or
When processing incoming gossip, clients MAY descore or disconnect peers who fail to observe these constraints.
For any optional queueing, clients SHOULD maintain maximum queue sizes to avoid DoS vectors.
Gossipsub v1.1 introduces [Extended Validators](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators)
for the application to aid in the gossipsub peer-scoring scheme.
We utilize `ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are application specific validations.
@ -284,12 +288,20 @@ Signed blocks are sent in their entirety.
The following validations MUST pass before forwarding the `signed_beacon_block` on the network.
- _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot).
i.e. validate that `signed_beacon_block.message.slot <= current_slot`
(a client MAY queue future blocks for processing at the appropriate slot).
- _[IGNORE]_ The block is from a slot greater than the latest finalized slot --
i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)`
(a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc).
- _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`.
- _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey.
- _[IGNORE]_ The block's parent (defined by `block.parent_root`) has been seen
(via both gossip and non-gossip sources)
(a client MAY queue blocks for processing once the parent block is retrieved).
- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes validation.
- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of `block` -- i.e.
`get_ancestor(store, block.parent_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch))
== store.finalized_checkpoint.root`
- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot
in the context of the current shuffling (defined by `parent_root`/`slot`).
If the `proposer_index` cannot immediately be verified against the expected shuffling,
@ -310,7 +322,6 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_
(via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally).
- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator
with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`.
- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
- _[REJECT]_ The attestation has participants --
that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`.
- _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot --
@ -321,6 +332,14 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_
of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`.
- _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, is valid.
- _[REJECT]_ The signature of `aggregate` is valid.
- _[IGNORE]_ The block being voted for (`aggregate.data.beacon_block_root`) has been seen
(via both gossip and non-gossip sources)
(a client MAY queue aggregates for processing once block is retrieved).
- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation.
- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e.
`get_ancestor(store, aggregate.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch))
== store.finalized_checkpoint.root`
##### `voluntary_exit`
@ -376,8 +395,16 @@ The following validations MUST pass before forwarding the `attestation` on the s
that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set).
- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet
that has an identical `attestation.data.target.epoch` and participating validator index.
- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation.
- _[REJECT]_ The signature of `attestation` is valid.
- _[IGNORE]_ The block being voted for (`attestation.data.beacon_block_root`) has been seen
(via both gossip and non-gossip sources)
(a client MAY queue aggregates for processing once block is retrieved).
- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation.
- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `attestation.data.beacon_block_root` -- i.e.
`get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch))
== store.finalized_checkpoint.root`
#### Attestations and Aggregation
@ -535,20 +562,19 @@ Clients MUST treat as valid any byte sequences.
### Encoding strategies
The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction.
Two values are possible at this time:
Only one value is possible at this time:
- `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md).
This encoding type MUST be supported by all clients.
- `ssz_snappy`: The contents are first [SSZ-encoded](../../ssz/simple-serialize.md)
and then compressed with [Snappy](https://github.com/google/snappy) frames compression.
For objects containing a single field, only the field is SSZ-encoded not a container with a single field.
For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s.
- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) frames compression.
This encoding type MUST be supported by all clients.
#### SSZ-encoding strategy (with or without Snappy)
#### SSZ-snappy encoding strategy
The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded.
If the Snappy variant is selected, we feed the serialized form of the object to the Snappy compressor on encoding.
To achieve snappy encoding on top of SSZ, we feed the serialized form of the object to the Snappy compressor on encoding.
The inverse happens on decoding.
Snappy has two formats: "block" and "frames" (streaming).
@ -557,14 +583,14 @@ To support large requests and response chunks, snappy-framing is used.
Since snappy frame contents [have a maximum size of `65536` bytes](https://github.com/google/snappy/blob/master/framing_format.txt#L104)
and frame headers are just `identifier (1) + checksum (4)` bytes, the expected buffering of a single frame is acceptable.
**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST encode the length of the raw SSZ bytes,
**Encoding-dependent header:** Req/Resp protocols using the `ssz_snappy` encoding strategy MUST encode the length of the raw SSZ bytes,
encoded as an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints).
*Writing*: By first computing and writing the SSZ byte length, the SSZ encoder can then directly write the chunk contents to the stream.
If Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame.
When Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame.
*Reading*: After reading the expected SSZ byte length, the SSZ decoder can directly read the contents from the stream.
If snappy is applied, it can be passed through a buffered Snappy reader to decompress frame by frame.
When snappy is applied, it can be passed through a buffered Snappy reader to decompress frame by frame.
Before reading the payload, the header MUST be validated:
- The unsigned protobuf varint used for the length-prefix MUST not be longer than 10 bytes, which is sufficient for any `uint64`.
@ -573,7 +599,6 @@ Before reading the payload, the header MUST be validated:
After reading a valid header, the payload MAY be read, while maintaining the size constraints from the header.
A reader SHOULD NOT read more than `max_encoded_len(n)` bytes after reading the SSZ length-prefix `n` from the header.
- For `ssz` this is: `n`
- For `ssz_snappy` this is: `32 + n + n // 6`.
This is considered the [worst-case compression result](https://github.com/google/snappy/blob/537f4ad6240e586970fe554614542e9717df7902/snappy.cc#L98) by Snappy.
@ -1146,6 +1171,25 @@ Some examples of where messages could be duplicated:
Partial aggregates could be duplicated
* Clients re-publishing seen messages
### Why are these specific gossip parameters chosen?
- `D`, `D_low`, `D_high`, `D_lazy`: recommended defaults.
- `heartbeat_interval`: 0.7 seconds, recommended for eth2 in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4).
- `fanout_ttl`: 60 seconds, recommended default.
Fanout is primarily used by committees publishing attestations to subnets.
This happens once per epoch per validator and the subnet changes each epoch
so there is little to gain in having a `fanout_ttl` be increased from the recommended default.
- `mcache_len`: 6, increase by one to ensure that mcache is around for long
enough for `IWANT`s to respond to `IHAVE`s in the context of the shorter
`heartbeat_interval`. If `mcache_gossip` is increased, this param should be
increased to be at least `3` (~2 seconds) more than `mcache_gossip`.
- `mcache_gossip`: 3, recommended default. This can be increased to 5 or 6
(~4 seconds) if gossip times are longer than expected and the current window
does not provide enough responsiveness during adverse conditions.
- `seen_ttl`: `SLOTS_PER_EPOCH * SECONDS_PER_SLOT / heartbeat_interval = approx. 550`.
Attestation gossip validity is bounded by an epoch, so this is the safe max bound.
### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?
For some gossip channels (e.g. those for Attestations and BeaconBlocks),
@ -1265,7 +1309,7 @@ Thus, libp2p transparently handles message delimiting in the underlying stream.
libp2p streams are full-duplex, and each party is responsible for closing their write side (like in TCP).
We can therefore use stream closure to mark the end of the request and response independently.
Nevertheless, in the case of `ssz` and `ssz_snappy`, messages are still length-prefixed with the length of the underlying data:
Nevertheless, in the case of `ssz_snappy`, messages are still length-prefixed with the length of the underlying data:
* A basic reader can prepare a correctly sized buffer before reading the message
* A more advanced reader can stream-decode SSZ given the length of the SSZ data.
* Alignment with protocols like gRPC over HTTP/2 that prefix with length

View File

@ -111,7 +111,7 @@ The validator constructs their `withdrawal_credentials` via the following:
### Submit deposit
In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 proof-of-work chain. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`.
In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID`. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`.
To submit a deposit:
@ -273,7 +273,7 @@ An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state)` wher
```python
def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64:
return state.genesis_time + slot * SECONDS_PER_SLOT
return uint64(state.genesis_time + slot * SECONDS_PER_SLOT)
```
```python
@ -399,7 +399,7 @@ Set `attestation_data.beacon_block_root = hash_tree_root(head_block)`.
*Note*: `epoch_boundary_block_root` can be looked up in the state using:
- Let `start_slot = compute_start_slot_at_epoch(get_current_epoch(head_state))`.
- Let `epoch_boundary_block_root = hash_tree_root(head_block) if start_slot == head_state.slot else get_block_root(state, start_slot)`.
- Let `epoch_boundary_block_root = hash_tree_root(head_block) if start_slot == head_state.slot else get_block_root(state, get_current_epoch(head_state))`.
#### Construct attestation
@ -443,7 +443,7 @@ def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, comm
slots_since_epoch_start = slot % SLOTS_PER_EPOCH
committees_since_epoch_start = committees_per_slot * slots_since_epoch_start
return (committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT
return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT)
```
### Attestation aggregation
@ -465,7 +465,7 @@ def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSigna
def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool:
committee = get_beacon_committee(state, slot, index)
modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE)
return bytes_to_int(hash(slot_signature)[0:8]) % modulo == 0
return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0
```
#### Construct aggregate

View File

@ -35,7 +35,6 @@
- [`ShardState`](#shardstate)
- [`ShardTransition`](#shardtransition)
- [`CompactCommittee`](#compactcommittee)
- [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper)
- [Helper functions](#helper-functions)
- [Misc](#misc-1)
- [`compute_previous_slot`](#compute_previous_slot)
@ -408,15 +407,6 @@ class CompactCommittee(Container):
compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
```
### `AttestationCustodyBitWrapper`
```python
class AttestationCustodyBitWrapper(Container):
attestation_data_root: Root
block_index: uint64
bit: boolean
```
## Helper functions
### Misc
@ -581,8 +571,8 @@ def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard
"""
epoch = compute_epoch_at_slot(slot)
committee = get_shard_committee(beacon_state, epoch, shard)
seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE) + int_to_bytes(slot, length=8))
r = bytes_to_int(seed[:8])
seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_COMMITTEE) + uint_to_bytes(slot))
r = bytes_to_uint64(seed[:8])
return committee[r % len(committee)]
```
@ -760,20 +750,24 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
committee = get_beacon_committee(state, data.slot, data.index)
assert len(attestation.aggregation_bits) == len(committee)
if attestation.data.target.epoch == get_current_epoch(state):
assert attestation.data.source == state.current_justified_checkpoint
if data.target.epoch == get_current_epoch(state):
assert data.source == state.current_justified_checkpoint
else:
assert attestation.data.source == state.previous_justified_checkpoint
assert data.source == state.previous_justified_checkpoint
# Type 1: on-time attestations
if is_on_time_attestation(state, attestation.data):
if is_on_time_attestation(state, data):
# Correct parent block root
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
# Correct shard number
shard = compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
assert attestation.data.shard == shard
# On-time attestations should have a non-empty shard transition root
assert attestation.data.shard_transition_root != hash_tree_root(ShardTransition())
shard = compute_shard_from_committee_index(state, data.index, data.slot)
assert data.shard == shard
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if data.slot > GENESIS_SLOT:
# On-time attestations should have a non-empty shard transition root
assert data.shard_transition_root != hash_tree_root(ShardTransition())
else:
assert data.shard_transition_root == hash_tree_root(ShardTransition())
# Type 2: no shard transition
else:
# Ensure delayed attestation
@ -811,7 +805,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
```python
def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None:
# TODO: only need to check it once when phase 1 starts
assert state.slot > PHASE_1_GENESIS_SLOT
assert state.slot > PHASE_1_FORK_SLOT
# Correct data root count
offset_slots = get_offset_slots(state, shard)
@ -863,9 +857,10 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr
# Verify combined proposer signature
assert optional_aggregate_verify(pubkeys, signing_roots, transition.proposer_signature_aggregate)
# Save updated state
state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1]
state.shard_states[shard].slot = compute_previous_slot(state.slot)
# Copy and save updated shard state
shard_state = copy(transition.shard_states[len(transition.shard_states) - 1])
shard_state.slot = compute_previous_slot(state.slot)
state.shard_states[shard] = shard_state
```
###### `process_crosslink_for_shard`
@ -976,8 +971,11 @@ def verify_empty_shard_transition(state: BeaconState, shard_transitions: Sequenc
def process_shard_transitions(state: BeaconState,
shard_transitions: Sequence[ShardTransition],
attestations: Sequence[Attestation]) -> None:
# Process crosslinks
process_crosslinks(state, shard_transitions, attestations)
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if compute_previous_slot(state.slot) > GENESIS_SLOT:
# Process crosslinks
process_crosslinks(state, shard_transitions, attestations)
# Verify the empty proposal shard states
assert verify_empty_shard_transition(state, shard_transitions)
```

View File

@ -25,7 +25,6 @@
- [`CustodyKeyReveal`](#custodykeyreveal)
- [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal)
- [Helpers](#helpers)
- [`get_block_data_merkle_root`](#get_block_data_merkle_root)
- [`replace_empty_or_append`](#replace_empty_or_append)
- [`legendre_bit`](#legendre_bit)
- [`get_custody_atoms`](#get_custody_atoms)
@ -73,7 +72,6 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**14` (= 16,384) | epochs | ~73 days |
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**15` (= 32,768) | epochs | ~146 days |
| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
### Max operations per block
@ -127,7 +125,7 @@ class CustodyChunkResponse(Container):
challenge_index: uint64
chunk_index: uint64
chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK]
branch: Vector[Root, CUSTODY_RESPONSE_DEPTH]
branch: Vector[Root, CUSTODY_RESPONSE_DEPTH + 1]
```
#### `CustodySlashing`
@ -180,13 +178,8 @@ class EarlyDerivedSecretReveal(Container):
mask: Bytes32
```
## Helpers
### `get_block_data_merkle_root`
`get_block_data_merkle_root(data: ByteList) -> Root` is the function that returns the Merkle root of the block data without the length mix-in.
### `replace_empty_or_append`
```python
@ -291,7 +284,7 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat
### `get_custody_period_for_validator`
```python
def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> int:
def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> uint64:
'''
Return the reveal period for a given validator.
'''
@ -381,7 +374,7 @@ def process_chunk_challenge_response(state: BeaconState,
assert is_valid_merkle_branch(
leaf=hash_tree_root(response.chunk),
branch=response.branch,
depth=CUSTODY_RESPONSE_DEPTH,
depth=CUSTODY_RESPONSE_DEPTH + 1, # Add 1 for the List length mix-in
index=response.chunk_index,
root=challenge.data_root,
)
@ -522,12 +515,8 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
shard_transition = custody_slashing.shard_transition
assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root
# Verify that the provided data matches the shard-transition
assert (
get_block_data_merkle_root(custody_slashing.data)
== shard_transition.shard_data_roots[custody_slashing.data_index]
)
assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index]
assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index]
# Verify existence and participation of claimed malefactor
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
assert custody_slashing.malefactor_index in attesters

View File

@ -9,8 +9,13 @@
- [Introduction](#introduction)
- [Helpers](#helpers)
- [Extended `LatestMessage`](#extended-latestmessage)
- [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 -->
@ -20,17 +25,74 @@
This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.
### Helpers
### Updated data structures
#### Extended `LatestMessage`
#### 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 LatestMessage(object):
class ShardLatestMessage(object):
epoch: Epoch
root: Root
```
#### `ShardStore`
```python
@dataclass
class ShardStore:
shard: Shard
shard_root: Root
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) -> Store:
anchor_block_header = anchor_state.latest_block_header.copy()
if anchor_block_header.state_root == Bytes32():
anchor_block_header.state_root = hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block_header)
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: anchor_block_header},
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`
@ -43,7 +105,7 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn
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=shard, shard_root=attestation.data.shard_head_root
)
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
shard_latest_message = ShardLatestMessage(epoch=target.epoch, root=attestation.data.shard_head_root)
store.shard_stores[shard].latest_messages[i] = shard_latest_message
```

View File

@ -35,18 +35,18 @@ Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
| `PHASE_1_GENESIS_SLOT` | `2**5` **TBD** |
| `PHASE_1_FORK_SLOT` | `Slot(0)` **TBD** |
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
## 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_GENESIS_SLOT`, where `PHASE_1_GENESIS_SLOT % SLOTS_PER_EPOCH == 0`.
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_GENESIS_SLOT`, an irregular state change is made to upgrade to Phase 1.
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:
@ -102,7 +102,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
current_epoch_start_shard=Shard(0),
shard_states=List[ShardState, MAX_SHARDS](
ShardState(
slot=pre.slot,
slot=compute_previous_slot(pre.slot),
gasprice=MIN_GASPRICE,
latest_block_root=Root(),
) for i in range(INITIAL_ACTIVE_SHARDS)

View File

@ -11,7 +11,6 @@
- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [`ShardStore`](#shardstore)
- [`get_forkchoice_shard_store`](#get_forkchoice_shard_store)
- [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance)
- [`get_shard_head`](#get_shard_head)
@ -30,16 +29,6 @@ This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase
### Helpers
#### `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)
```
#### `get_forkchoice_shard_store`
```python
@ -48,7 +37,7 @@ def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> Shard
shard=shard,
signed_blocks={
anchor_state.shard_states[shard].latest_block_root: SignedShardBlock(
message=ShardBlock(slot=anchor_state.slot, shard=shard)
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]},
@ -58,18 +47,21 @@ def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> Shard
#### `get_shard_latest_attesting_balance`
```python
def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, root: Root) -> Gwei:
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 store.latest_messages
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 store.latest_messages[i].shard == shard_store.shard
and get_shard_ancestor(
store, shard_store, store.latest_messages[i].shard_root, shard_store.signed_blocks[root].message.slot
store,
shard,
shard_store.latest_messages[i].root,
shard_store.signed_blocks[root].message.slot,
) == root
)
))
@ -78,10 +70,14 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro
#### `get_shard_head`
```python
def get_shard_head(store: Store, shard_store: ShardStore) -> Root:
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_store.shard]
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()
@ -97,17 +93,18 @@ def get_shard_head(store: Store, shard_store: ShardStore) -> Root:
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_store, root), root)
children, key=lambda root: (get_shard_latest_attesting_balance(store, shard, root), root)
)
```
#### `get_shard_ancestor`
```python
def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: Slot) -> Root:
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_store, block.shard_parent_root, slot)
return get_shard_ancestor(store, shard, block.shard_parent_root, slot)
elif block.slot == slot:
return root
else:
@ -118,17 +115,17 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot:
#### `get_pending_shard_blocks`
```python
def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[SignedShardBlock]:
def get_pending_shard_blocks(store: Store, shard: Shard) -> Sequence[SignedShardBlock]:
"""
Return the canonical shard block branch that has not yet been crosslinked.
"""
shard = shard_store.shard
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_store)
shard_head_root = get_shard_head(store, shard)
root = shard_head_root
signed_shard_blocks = []
while root != latest_shard_block_root:
@ -145,13 +142,10 @@ def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[
#### `on_shard_block`
```python
def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: SignedShardBlock) -> None:
def on_shard_block(store: Store, signed_shard_block: SignedShardBlock) -> None:
shard_block = signed_shard_block.message
shard = shard_store.shard
# Check shard
# TODO: check it in networking spec
assert shard_block.shard == shard
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

View File

@ -31,7 +31,7 @@
- [`FullAttestation`](#fullattestation)
- [Timing](#timing)
- [Attestation data](#attestation-data)
- [Head shard root](#head-shard-root)
- [Shard head root](#shard-head-root)
- [Shard transition](#shard-transition)
- [Construct attestation](#construct-attestation)
- [Attestation Aggregation](#attestation-aggregation)
@ -267,9 +267,9 @@ A validator should create and broadcast the `attestation` to the associated atte
*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.
##### Head shard root
##### Shard head root
Set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`.
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
@ -294,7 +294,7 @@ def get_shard_transition_fields(
for slot in offset_slots:
if slot in shard_block_slots:
shard_block = shard_blocks[shard_block_slots.index(slot)]
shard_data_roots.append(get_block_data_merkle_root(shard_block.message.body))
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())
@ -310,6 +310,10 @@ def get_shard_transition_fields(
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),
@ -463,7 +467,7 @@ def get_light_client_slot_signature(state: BeaconState, slot: Slot, privkey: int
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_int(hash(slot_signature)[0:8]) % modulo == 0
return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0
```
#### Construct aggregate

View File

@ -1 +1 @@
0.12.1
0.12.2

View File

@ -59,8 +59,8 @@ def bls_default(request):
def bls_type(request):
bls_type = request.config.getoption("--bls-type")
if bls_type == "py_ecc":
bls_utils.bls = bls_utils.py_ecc_bls
bls_utils.use_py_ecc()
elif bls_type == "milagro":
bls_utils.bls = bls_utils.milagro_bls
bls_utils.use_milagro()
else:
raise Exception(f"unrecognized bls type: {bls_type}")

View File

@ -63,9 +63,6 @@ def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Ca
# 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)
# Shard state slot must lag behind BeaconState slot by at least 1
# Will handle this more elegantly with fork mechanics
spec.process_slots(state, state.slot + 1)
return state

View File

@ -1,129 +0,0 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls
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
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, shard_store, signed_block, valid=True):
if not valid:
try:
spec.on_shard_block(store, shard_store, signed_block)
except AssertionError:
return
else:
assert False
spec.on_shard_block(store, shard_store, signed_block)
assert shard_store.signed_blocks[hash_tree_root(signed_block.message)] == signed_block
def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_blocks_buffer):
shard = shard_store.shard
body = b'\x56' * 4
shard_head_root = spec.get_shard_head(store, shard_store)
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_store, shard_block)
assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root()
def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer):
pending_shard_blocks = spec.get_pending_shard_blocks(store, shard_store)
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 apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer):
store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
shard = shard_store.shard
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` function
check_pending_shard_blocks(spec, store, shard_store, 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=False,
)
assert attestation.data.shard == shard
beacon_block.body.attestations = [attestation]
beacon_block.body.shard_transitions = shard_transitions
# Clear buffer
shard_blocks_buffer.clear()
signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) # transition!
add_block_to_store(spec, store, signed_beacon_block)
assert spec.get_head(store) == beacon_block.hash_tree_root()
# On shard block at transitioned `state.slot`
if is_in_offset_sets(spec, state, shard):
# The created shard block would be appended to `shard_blocks_buffer`
apply_shard_block(spec, store, shard_store, state, shard_blocks_buffer)
return has_shard_committee
@with_all_phases_except([PHASE0])
@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 = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
shard_store = spec.get_forkchoice_shard_store(state, shard)
shard_head_root = spec.get_shard_head(store, shard_store)
assert shard_head_root == state.shard_states[shard].latest_block_root
assert shard_store.block_states[shard_head_root].slot == 1
assert shard_store.block_states[shard_head_root] == state.shard_states[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 = []
while shard_committee_counter > 0:
has_shard_committee = apply_shard_and_beacon(
spec, state, store, shard_store, shard_blocks_buffer
)
if has_shard_committee:
shard_committee_counter -= 1

View File

@ -69,7 +69,7 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio
source_epoch = state.current_justified_checkpoint.epoch
source_root = state.current_justified_checkpoint.root
attestation_data = spec.AttestationData(
data = spec.AttestationData(
slot=slot,
index=index,
beacon_block_root=block_root,
@ -79,23 +79,27 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio
if spec.fork == PHASE1:
if shard is None:
shard = spec.compute_shard_from_committee_index(state, attestation_data.index, attestation_data.slot)
attestation_data.shard = shard
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
attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
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:
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
last_offset_index = len(shard_transition.shard_data_roots) - 1
attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
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:
attestation_data.shard_head_root = state.shard_states[shard].latest_block_root
attestation_data.shard_transition_root = spec.Root()
return attestation_data
data.shard_head_root = state.shard_states[shard].latest_block_root
data.shard_transition_root = spec.Root()
return data
def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
@ -189,19 +193,6 @@ def sign_indexed_attestation(spec, state, indexed_attestation):
indexed_attestation.signature = sign_aggregate_attestation(spec, state, data, participants)
def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey):
domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
signing_root = spec.compute_signing_root(
spec.AttestationCustodyBitWrapper(
attestation_data_root=attestation_data.hash_tree_root(),
block_index=block_index,
bit=bit,
),
domain,
)
return bls.Sign(privkey, signing_root)
def sign_attestation(spec, state, attestation):
participants = spec.get_attesting_indices(
state,

View File

@ -113,7 +113,7 @@ def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_i
def custody_chunkify(spec, x):
chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)]
chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0")
return chunks
return [ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](c) for c in chunks]
def build_proof(anchor, leaf_index):
@ -149,12 +149,14 @@ def get_valid_custody_chunk_response(spec, state, chunk_challenge, challenge_ind
chunk_index = chunk_challenge.chunk_index
data_branch = build_proof(custody_data_block.get_backing().get_left(), chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH)
leaf_index = chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH
serialized_length = len(custody_data_block).to_bytes(32, 'little')
data_branch = build_proof(custody_data_block.get_backing().get_left(), leaf_index) + [serialized_length]
return spec.CustodyChunkResponse(
challenge_index=challenge_index,
chunk_index=chunk_index,
chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
chunk=chunks[chunk_index],
branch=data_branch,
)
@ -165,7 +167,7 @@ def get_custody_test_vector(bytelength, offset=0):
def get_sample_shard_transition(spec, start_slot, block_lengths):
b = [spec.get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)))
b = [spec.hash_tree_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)))
for x in block_lengths]
shard_transition = spec.ShardTransition(
start_slot=start_slot,
@ -200,5 +202,5 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust
slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret,
block_lengths[0], slashable=slashable)
block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector)
shard_transition.shard_data_roots[0] = spec.get_block_data_merkle_root(block_data)
shard_transition.shard_data_roots[0] = spec.hash_tree_root(block_data)
return shard_transition, slashable_test_vector

View File

@ -42,12 +42,10 @@ def transition_to_slot_via_block(spec, state, slot):
def transition_to_valid_shard_slot(spec, state):
"""
Transition to slot `spec.PHASE_1_GENESIS_SLOT + 1` and fork at `spec.PHASE_1_GENESIS_SLOT`.
Transition to slot `spec.PHASE_1_FORK_SLOT + 1` and fork at `spec.PHASE_1_FORK_SLOT`.
"""
transition_to(spec, state, spec.PHASE_1_GENESIS_SLOT)
state = spec.upgrade_to_phase1(state) # `upgrade_to_phase1` is a pure function
transition_to(spec, state, spec.PHASE_1_FORK_SLOT)
next_slot(spec, state)
return state
def next_epoch(spec, state):

View File

@ -1,7 +1,6 @@
from eth2spec.test.context import (
PHASE0,
spec_state_test, spec_test,
with_all_phases, with_phases, single_phase,
with_all_phases, single_phase,
with_custom_state,
zero_activation_threshold,
misc_balances, low_single_balance,
@ -25,7 +24,7 @@ def run_process_rewards_and_penalties(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties')
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_genesis_epoch_no_attestations_no_penalties(spec, state):
pre_state = state.copy()
@ -38,7 +37,7 @@ def test_genesis_epoch_no_attestations_no_penalties(spec, state):
assert state.balances[index] == pre_state.balances[index]
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_genesis_epoch_full_attestations_no_rewards(spec, state):
attestations = []
@ -48,7 +47,7 @@ def test_genesis_epoch_full_attestations_no_rewards(spec, state):
attestation = get_valid_attestation(spec, state, signed=True)
attestations.append(attestation)
# fill each created slot in state after inclusion delay
if slot - spec.MIN_ATTESTATION_INCLUSION_DELAY >= 0:
if slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
include_att = attestations[slot - spec.MIN_ATTESTATION_INCLUSION_DELAY]
add_attestations_to_state(spec, state, [include_att], state.slot)
next_slot(spec, state)

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import PHASE0, spec_state_test, never_bls, with_all_phases, with_phases
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.helpers.state import next_epoch_via_block
from eth2spec.test.helpers.attestations import next_epoch_with_attestations
@ -28,9 +28,8 @@ def check_finality(spec,
assert state.finalized_checkpoint == prev_state.finalized_checkpoint
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@never_bls
def test_finality_no_updates_at_genesis(spec, state):
assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH
@ -54,7 +53,6 @@ def test_finality_no_updates_at_genesis(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_finality_rule_4(spec, state):
# get past first two epochs that finality does not run on
next_epoch_via_block(spec, state)
@ -80,7 +78,6 @@ def test_finality_rule_4(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_finality_rule_1(spec, state):
# get past first two epochs that finality does not run on
next_epoch_via_block(spec, state)
@ -108,7 +105,6 @@ def test_finality_rule_1(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_finality_rule_2(spec, state):
# get past first two epochs that finality does not run on
next_epoch_via_block(spec, state)
@ -138,7 +134,6 @@ def test_finality_rule_2(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_finality_rule_3(spec, state):
"""
Test scenario described here

View File

@ -313,6 +313,28 @@ def test_empty_epoch_transition_not_finalizing(spec, state):
assert state.balances[index] < pre_balances[index]
@with_all_phases
@spec_state_test
def test_proposer_self_slashing(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
assert not state.validators[block.proposer_index].slashed
proposer_slashing = get_valid_proposer_slashing(
spec, state, slashed_index=block.proposer_index, signed_1=True, signed_2=True)
block.body.proposer_slashings.append(proposer_slashing)
# The header is processed *before* the block body:
# the proposer was not slashed before the body, thus the block is valid.
signed_block = state_transition_and_sign_block(spec, state, block)
# The proposer slashed themselves.
assert state.validators[block.proposer_index].slashed
yield 'blocks', [signed_block]
yield 'post', state
@with_all_phases
@spec_state_test
def test_proposer_slashing(spec, state):
@ -484,7 +506,7 @@ def test_duplicate_attester_slashing(spec, state):
# All AttesterSlashing tests should be adopted for Phase 1 but helper support is not yet there
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_multiple_attester_slashings_no_overlap(spec, state):
# Skip test if config cannot handle multiple AttesterSlashings per block
@ -525,7 +547,7 @@ def test_multiple_attester_slashings_no_overlap(spec, state):
check_attester_slashing_effect(spec, pre_state, state, full_indices)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_multiple_attester_slashings_partial_overlap(spec, state):
# Skip test if config cannot handle multiple AttesterSlashings per block
@ -768,7 +790,7 @@ def test_voluntary_exit(spec, state):
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_double_validator_exit_same_block(spec, state):
validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]

View File

@ -26,9 +26,12 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
shard=attestation.data.shard,
shard_root=attestation.data.shard_head_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
assert (
store.latest_messages[sample_index] == latest_message

View File

@ -40,9 +40,9 @@ def run_is_candidate_block(spec, eth1_block, period_start, success=True):
def get_min_new_period_epochs(spec):
return int(
spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2 # to seconds
/ spec.SECONDS_PER_SLOT / spec.SLOTS_PER_EPOCH
return (
(spec.SECONDS_PER_ETH1_BLOCK * spec.ETH1_FOLLOW_DISTANCE * 2) # to seconds
// spec.SECONDS_PER_SLOT // spec.SLOTS_PER_EPOCH
)

View File

@ -6,7 +6,7 @@ from eth2spec.test.helpers.custody import (
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
PHASE0,
with_all_phases_except,
@ -68,7 +68,8 @@ def run_custody_chunk_response_processing(spec, state, custody_response, valid=T
@with_all_phases_except([PHASE0])
@spec_state_test
def test_challenge_appended(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -89,7 +90,8 @@ def test_challenge_appended(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_challenge_empty_element_replaced(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -112,7 +114,8 @@ def test_challenge_empty_element_replaced(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_duplicate_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -135,7 +138,8 @@ def test_duplicate_challenge(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_second_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -160,6 +164,7 @@ def test_second_challenge(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_multiple_epochs_custody(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
shard = 0
@ -182,6 +187,7 @@ def test_multiple_epochs_custody(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_many_epochs_custody(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
shard = 0
@ -204,6 +210,7 @@ def test_many_epochs_custody(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_off_chain_attestation(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
@ -222,6 +229,7 @@ def test_off_chain_attestation(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_custody_response(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
@ -248,9 +256,39 @@ def test_custody_response(spec, state):
yield from run_custody_chunk_response_processing(spec, state, custody_response)
@with_all_phases_except([PHASE0])
@spec_state_test
def test_custody_response_chunk_index_2(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
_, _, _ = run_attestation_processing(spec, state, attestation)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=2)
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
chunk_challenge_index = state.custody_chunk_challenge_index - 1
custody_response = get_valid_custody_chunk_response(
spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3)
yield from run_custody_chunk_response_processing(spec, state, custody_response)
@with_all_phases_except([PHASE0])
@spec_state_test
def test_custody_response_multiple_epochs(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
shard = 0
@ -280,6 +318,7 @@ def test_custody_response_multiple_epochs(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_custody_response_many_epochs(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
shard = 0

View File

@ -63,6 +63,7 @@ def run_standard_custody_slashing_test(spec,
slashing_message_data=None,
correct=True,
valid=True):
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
if shard_lateness is None:
shard_lateness = spec.SLOTS_PER_EPOCH
transition_to(spec, state, state.slot + shard_lateness)
@ -124,7 +125,7 @@ def test_multiple_epochs_custody(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_many_epochs_custody(spec, state):
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10)
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 5)
@with_all_phases_except([PHASE0])

View File

@ -22,7 +22,7 @@ from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard
def get_initial_env(spec, state, target_len_offset_slot):
state = transition_to_valid_shard_slot(spec, state)
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)

View File

@ -5,7 +5,7 @@ from eth2spec.test.helpers.custody import (
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
PHASE0,
with_all_phases_except,
@ -26,7 +26,8 @@ def run_process_challenge_deadlines(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_validator_slashed_after_chunk_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))

View File

@ -10,7 +10,7 @@ from eth2spec.test.helpers.custody import (
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.state import next_epoch_via_block, transition_to
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,
spec_state_test,
@ -32,6 +32,8 @@ def run_process_custody_final_updates(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_validator_withdrawal_delay(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
spec.initiate_validator_exit(state, 0)
assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
@ -43,6 +45,8 @@ def test_validator_withdrawal_delay(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_validator_withdrawal_reenable_after_custody_reveal(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
spec.initiate_validator_exit(state, 0)
assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH
@ -66,7 +70,8 @@ def test_validator_withdrawal_reenable_after_custody_reveal(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -114,7 +119,8 @@ def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))

View File

@ -105,7 +105,7 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
committee_index = spec.CommitteeIndex(0)
@ -123,7 +123,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
committee_index = spec.CommitteeIndex(0)
@ -146,7 +146,7 @@ def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
# build shard block
shard = 0
@ -179,7 +179,7 @@ def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_custody_key_reveal(spec, state):
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
block = build_empty_block(spec, state, slot=state.slot + 1)
@ -192,7 +192,7 @@ def test_custody_key_reveal(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_early_derived_secret_reveal(spec, state):
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
block = build_empty_block(spec, state, slot=state.slot + 1)
early_derived_secret_reveal = get_valid_early_derived_secret_reveal(spec, state)
block.body.early_derived_secret_reveals = [early_derived_secret_reveal]
@ -208,7 +208,7 @@ def test_custody_slashing(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
# Build shard block
shard = 0

View File

@ -51,10 +51,12 @@ def test_valid_shard_block(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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 = 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)
@ -71,7 +73,9 @@ def test_invalid_shard_parent_root(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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)
@ -88,7 +92,8 @@ def test_invalid_beacon_parent_root(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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)
@ -105,7 +110,8 @@ def test_invalid_slot(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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)
@ -123,7 +129,8 @@ def test_invalid_proposer_index(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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)
@ -147,7 +154,8 @@ def test_out_of_bound_offset(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
slot = (
beacon_state.shard_states[shard].slot
@ -170,7 +178,8 @@ def test_invalid_offset(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(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
@ -191,7 +200,8 @@ def test_empty_block_body(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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, body=b'', signed=True)
@ -212,7 +222,8 @@ def test_invalid_signature(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_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=False)
@ -233,7 +244,8 @@ def test_max_offset(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(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)
@ -253,7 +265,8 @@ def test_pending_shard_parent_block(spec, state):
return
# Block N
beacon_state = transition_to_valid_shard_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_1 = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)

View File

@ -0,0 +1,280 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls
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
from eth2spec.test.helpers.shard_transitions import is_full_crosslink
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 = spec.get_forkchoice_store(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])
@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])
@spec_state_test
def test_shard_simple_fork(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
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])
@spec_state_test
def test_shard_latest_messages_for_different_shards(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here
state = spec.upgrade_to_phase1(state)
shard_0 = spec.Shard(0)
shard_1 = spec.Shard(1)
# Initialization
store = initialize_store(spec, state, [shard_0, shard_1])
# Shard 0 ----------------------------------
# Create fork on shard 0
_, forking_block = create_simple_fork(spec, state, store, shard_0)
# Vote for forking_block on shard 0
state = store.block_states[spec.get_head(store)].copy()
beacon_block = create_beacon_block_with_shard_transition(spec, state, store, shard_0, [forking_block],
is_checking_pending_shard_blocks=False)
store.time = store.time + spec.SECONDS_PER_SLOT
apply_all_attestation_to_store(spec, store, beacon_block.body.attestations)
# Head block of shard 0 has been changed due to the shard latest messages
assert spec.get_shard_head(store, shard_0) == forking_block.message.hash_tree_root()
# Shard 1 ----------------------------------
# Run shard 1 after 1~2 epochs
shard_committee_counter = 2
shard_blocks_buffer = [] # the accumulated shard blocks that haven't been crosslinked yet
while shard_committee_counter > 0:
has_shard_committee = create_and_apply_beacon_and_shard_blocks(
spec, state, store, shard_1, shard_blocks_buffer
)
if has_shard_committee:
shard_committee_counter -= 1
# Go back to see shard 0 ----------------------------------
# The head block of shard 0 should be unchanged.
assert spec.get_shard_head(store, shard_0) == forking_block.message.hash_tree_root()

View File

@ -14,6 +14,22 @@ Z2_SIGNATURE = b'\xc0' + b'\x00' * 95
STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE)
def use_milagro():
"""
Shortcut to use Milagro as BLS library
"""
global bls
bls = milagro_bls
def use_py_ecc():
"""
Shortcut to use Py-ecc as BLS library
"""
global bls
bls = py_ecc_bls
def only_with_bls(alt_return=None):
"""
Decorator factory to make a function only run when BLS is active. Otherwise return the default.

View File

@ -1,17 +1,9 @@
from hashlib import sha256
from typing import Dict, Union
from remerkleable.byte_arrays import Bytes32
from typing import Union
ZERO_BYTES32 = b'\x00' * 32
def _hash(x: Union[bytes, bytearray, memoryview]) -> bytes:
return sha256(x).digest()
hash_cache: Dict[bytes, bytes] = {}
def hash(x: bytes) -> bytes:
if x in hash_cache:
return hash_cache[x]
return _hash(x)
def hash(x: Union[bytes, bytearray, memoryview]) -> Bytes32:
return Bytes32(sha256(x).digest())

View File

@ -1,3 +1,6 @@
from typing import TypeVar
from remerkleable.basic import uint
from remerkleable.core import View
from remerkleable.byte_arrays import Bytes32
@ -8,3 +11,15 @@ def serialize(obj: View) -> bytes:
def hash_tree_root(obj: View) -> Bytes32:
return Bytes32(obj.get_backing().merkle_root())
def uint_to_bytes(n: uint) -> bytes:
return serialize(n)
V = TypeVar('V', bound=View)
# Helper method for typing copies, and avoiding a example_input.copy() method call, instead of copy(example_input)
def copy(obj: V) -> V:
return obj.copy()

View File

@ -5,4 +5,4 @@ from remerkleable.complex import Container, Vector, List
from remerkleable.basic import boolean, bit, uint, byte, uint8, uint16, uint32, uint64, uint128, uint256
from remerkleable.bitfields import Bitvector, Bitlist
from remerkleable.byte_arrays import ByteVector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, ByteList
from remerkleable.core import BasicView, View, TypeDef
from remerkleable.core import BasicView, View

View File

@ -0,0 +1,43 @@
# Finality tests
The aim of the tests for the finality rules.
- `finality`: transitions triggered by one or more blocks.
## Test case format
### `meta.yaml`
```yaml
description: string -- Optional. Description of test case, purely for debugging purposes.
bls_setting: int -- see general test-format spec.
blocks_count: int -- the number of blocks processed in this test.
```
### `pre.yaml`
A YAML-encoded `BeaconState`, the state before running the block transitions.
Also available as `pre.ssz`.
### `blocks_<index>.yaml`
A series of files, with `<index>` in range `[0, blocks_count)`. Blocks need to be processed in order,
following the main transition function (i.e. process slot and epoch transitions in between blocks as normal)
Each file is a YAML-encoded `SignedBeaconBlock`.
Each block is also available as `blocks_<index>.ssz`
### `post.yaml`
A YAML-encoded `BeaconState`, the state after applying the block transitions.
Also available as `post.ssz`.
## Condition
The resulting state should match the expected `post` state, or if the `post` state is left blank,
the handler should reject the series of blocks as invalid.

View File

@ -149,7 +149,9 @@ def case03_aggregate():
else:
raise Exception("Should have been INVALID")
yield f'aggregate_na_pubkeys', {
# No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID.
# https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8
yield f'aggregate_na_signatures', {
'input': [],
'output': None,
}
@ -319,6 +321,7 @@ def create_provider(handler_name: str,
if __name__ == "__main__":
bls.use_py_ecc() # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct.
gen_runner.run_generator("bls", [
create_provider('sign', case01_sign),
create_provider('verify', case02_verify),

View File

@ -7,6 +7,7 @@ from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
@ -14,6 +15,7 @@ def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str,
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
reload(spec_phase1)
bls.use_milagro()
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:

View File

@ -0,0 +1,5 @@
# Finality tests
Finality tests cover regular state-transitions in a common block-list format to test finality rules.
Information on the format of the tests can be found in the [finality test formats documentation](../../formats/finality/README.md).

View File

@ -0,0 +1,39 @@
from typing import Iterable
from importlib import reload
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from eth2spec.test.context import PHASE0
from eth2spec.test.phase0.finality import test_finality
from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.utils import bls
def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
reload(spec_phase1)
bls.use_milagro()
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
return generate_from_tests(
runner_name='finality',
handler_name=handler_name,
src=tests_src,
fork_name=PHASE0,
)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
if __name__ == "__main__":
gen_runner.run_generator("finality", [
create_provider('finality', test_finality, 'minimal'),
create_provider('finality', test_finality, 'mainnet'),
])

View File

@ -0,0 +1,2 @@
../../core/gen_helpers
../../../

View File

@ -1,13 +1,14 @@
from typing import Iterable
from eth2spec.test.context import PHASE0
from eth2spec.test.genesis import test_initialization, test_validity
from eth2spec.test.phase0.genesis import test_initialization, test_validity
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from eth2spec.phase0 import spec as spec
from importlib import reload
from eth2spec.config import config_util
from eth2spec.utils import bls
def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
@ -15,6 +16,7 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
reload(spec)
bls.use_milagro()
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:

View File

@ -7,6 +7,7 @@ from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
@ -14,6 +15,7 @@ def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str,
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
reload(spec_phase1)
bls.use_milagro()
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:

View File

@ -7,6 +7,7 @@ from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
@ -14,6 +15,7 @@ def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str,
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
reload(spec_phase1)
bls.use_milagro()
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:

View File

@ -7,6 +7,7 @@ from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
@ -14,6 +15,7 @@ def create_provider(fork_name: str, handler_name: str, tests_src_mod_name: str,
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
reload(spec_phase1)
bls.use_milagro()
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]: