Merge branch 'dev' into vbuterin-patch-3

This commit is contained in:
Danny Ryan 2019-07-30 13:32:58 -06:00
commit 6f62146905
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
100 changed files with 5885 additions and 3201 deletions

View File

@ -35,26 +35,26 @@ commands:
description: "Restore the cache with pyspec keys"
steps:
- restore_cached_venv:
venv_name: v2-pyspec
venv_name: v3-pyspec-bump2
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}
save_pyspec_cached_venv:
description: Save a venv into a cache with pyspec keys"
steps:
- save_cached_venv:
venv_name: v2-pyspec
venv_name: v3-pyspec-bump2
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }}
venv_path: ./test_libs/pyspec/venv
restore_deposit_contract_cached_venv:
description: "Restore the cache with deposit_contract keys"
steps:
- restore_cached_venv:
venv_name: v4-deposit-contract
venv_name: v6-deposit-contract
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }}
save_deposit_contract_cached_venv:
description: Save a venv into a cache with deposit_contract keys"
steps:
- save_cached_venv:
venv_name: v4-deposit-contract
venv_name: v6-deposit-contract
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }}
venv_path: ./deposit_contract/venv
jobs:

9
.gitignore vendored
View File

@ -9,8 +9,17 @@ build/
output/
eth2.0-spec-tests/
.pytest_cache
.mypy_cache
# Dynamically built from Markdown spec
test_libs/pyspec/eth2spec/phase0/spec.py
test_libs/pyspec/eth2spec/phase1/spec.py
# coverage reports
.htmlcov
.coverage
# local CI testing output
test_libs/pyspec/test-reports

View File

@ -21,17 +21,28 @@ PY_SPEC_PHASE_1_DEPS = $(SPEC_DIR)/core/1_*.md
PY_SPEC_ALL_TARGETS = $(PY_SPEC_PHASE_0_TARGETS) $(PY_SPEC_PHASE_1_TARGETS)
COV_HTML_OUT=.htmlcov
COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html
.PHONY: clean all test citest lint gen_yaml_tests pyspec phase0 phase1 install_test install_deposit_contract_test test_deposit_contract compile_deposit_contract
.PHONY: clean all test citest lint gen_yaml_tests pyspec phase0 phase1 install_test open_cov \
install_deposit_contract_test test_deposit_contract compile_deposit_contract
all: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS)
clean:
# deletes everything except the venvs
partial_clean:
rm -rf $(YAML_TEST_DIR)
rm -rf $(GENERATOR_VENVS)
rm -rf $(PY_SPEC_DIR)/venv $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(PY_SPEC_ALL_TARGETS)
rm -rf $(DEPOSIT_CONTRACT_DIR)/venv $(DEPOSIT_CONTRACT_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/$(COV_HTML_OUT)
rm -rf $(PY_SPEC_DIR)/.coverage
rm -rf $(PY_SPEC_DIR)/test-reports
clean: partial_clean
rm -rf $(PY_SPEC_DIR)/venv
rm -rf $(DEPOSIT_CONTRACT_DIR)/venv
# "make gen_yaml_tests" to run generators
gen_yaml_tests: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_TARGETS)
@ -41,15 +52,21 @@ install_test:
cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements-testing.txt;
test: $(PY_SPEC_ALL_TARGETS)
cd $(PY_SPEC_DIR); . venv/bin/activate; python -m pytest eth2spec
cd $(PY_SPEC_DIR); . venv/bin/activate; export PYTHONPATH="./"; \
python -m pytest -n 4 --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
citest: $(PY_SPEC_ALL_TARGETS)
cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; . venv/bin/activate; \
python -m pytest --junitxml=test-reports/eth2spec/test_results_phase0.xml eth2spec
python -m pytest -n 4 --junitxml=test-reports/eth2spec/test_results.xml eth2spec
open_cov:
((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) &
lint: $(PY_SPEC_ALL_TARGETS)
cd $(PY_SPEC_DIR); . venv/bin/activate; \
flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec;
flake8 --ignore=E252,W504,W503 --max-line-length=120 ./eth2spec \
&& cd ./eth2spec && mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase0 \
&& mypy --follow-imports=silent --warn-unused-ignores --ignore-missing-imports --check-untyped-defs --disallow-incomplete-defs --disallow-untyped-defs -p phase1;
install_deposit_contract_test: $(PY_SPEC_ALL_TARGETS)
cd $(DEPOSIT_CONTRACT_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements-testing.txt
@ -66,10 +83,10 @@ test_deposit_contract:
pyspec: $(PY_SPEC_ALL_TARGETS)
$(PY_SPEC_PHASE_0_TARGETS): $(PY_SPEC_PHASE_0_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p0 $(SPEC_DIR)/core/0_beacon-chain.md $@
python3 $(SCRIPT_DIR)/build_spec.py -p0 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/0_fork-choice.md $(SPEC_DIR)/validator/0_beacon-chain-validator.md $@
$(PY_SPEC_DIR)/eth2spec/phase1/spec.py: $(PY_SPEC_PHASE_1_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p1 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/1_custody-game.md $(SPEC_DIR)/core/1_shard-data-chains.md $@
python3 $(SCRIPT_DIR)/build_spec.py -p1 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/1_custody-game.md $(SPEC_DIR)/core/1_shard-data-chains.md $(SPEC_DIR)/core/0_fork-choice.md $@
CURRENT_DIR = ${CURDIR}

View File

@ -21,6 +21,12 @@ Core specifications for Eth 2.0 client validation can be found in [specs/core](s
* [Custody Game](specs/core/1_custody-game.md)
* [Shard Data Chains](specs/core/1_shard-data-chains.md)
### Phase 2
Phase 2 is still actively in R&D and does not yet have any formal specifications.
See the [Eth 2.0 Phase 2 Wiki](https://hackmd.io/UzysWse1Th240HELswKqVA?view) for current progress, discussions, and definitions regarding this work.
### Accompanying documents can be found in [specs](specs) and include:
* [SimpleSerialize (SSZ) spec](specs/simple-serialize.md)

View File

@ -10,23 +10,24 @@ SHARD_COUNT: 1024
# 2**7 (= 128)
TARGET_COMMITTEE_SIZE: 128
# 2**12 (= 4,096)
MAX_INDICES_PER_ATTESTATION: 4096
MAX_VALIDATORS_PER_COMMITTEE: 4096
# 2**2 (= 4)
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
CHURN_LIMIT_QUOTIENT: 65536
# Normalizes base rewards
BASE_REWARDS_PER_EPOCH: 5
# See issue 563
SHUFFLE_ROUND_COUNT: 90
# `2**16` (= 65,536)
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 65536
# Jan 3, 2020
MIN_GENESIS_TIME: 1578009600
# Deposit contract
# ---------------------------------------------------------------
# **TBD**
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890
# 2**5 (= 32)
DEPOSIT_CONTRACT_TREE_DEPTH: 32
# Gwei values
@ -43,20 +44,17 @@ EFFECTIVE_BALANCE_INCREMENT: 1000000000
# Initial values
# ---------------------------------------------------------------
GENESIS_FORK_VERSION: 0x00000000
# 0, GENESIS_EPOCH is derived from this constant
GENESIS_SLOT: 0
# 2**64 - 1
FAR_FUTURE_EPOCH: 18446744073709551615
BLS_WITHDRAWAL_PREFIX: 0
BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters
# ---------------------------------------------------------------
# 6 seconds 6 seconds
SECONDS_PER_SLOT: 6
# 2**2 (= 4) slots 24 seconds
MIN_ATTESTATION_INCLUSION_DELAY: 4
# 2**0 (= 1) slots 6 seconds
MIN_ATTESTATION_INCLUSION_DELAY: 1
# 2**6 (= 64) slots 6.4 minutes
SLOTS_PER_EPOCH: 64
# 2**0 (= 1) epochs 6.4 minutes
@ -75,24 +73,29 @@ PERSISTENT_COMMITTEE_PERIOD: 2048
MAX_EPOCHS_PER_CROSSLINK: 64
# 2**2 (= 4) epochs 25.6 minutes
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
# 2**14 (= 16,384) epochs ~73 days
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 16384
# State list lengths
# State vector lengths
# ---------------------------------------------------------------
# 2**16 (= 65,536) epochs ~0.8 years
EPOCHS_PER_HISTORICAL_VECTOR: 65536
# 2**13 (= 8,192) epochs ~36 days
LATEST_RANDAO_MIXES_LENGTH: 8192
# 2**13 (= 8,192) epochs ~36 days
LATEST_ACTIVE_INDEX_ROOTS_LENGTH: 8192
# 2**13 (= 8,192) epochs ~36 days
LATEST_SLASHED_EXIT_LENGTH: 8192
EPOCHS_PER_SLASHINGS_VECTOR: 8192
# 2**24 (= 16,777,216) historical roots, ~26,131 years
HISTORICAL_ROOTS_LIMIT: 16777216
# 2**40 (= 1,099,511,627,776) validator spots
VALIDATOR_REGISTRY_LIMIT: 1099511627776
# Reward and penalty quotients
# ---------------------------------------------------------------
# 2**5 (= 32)
BASE_REWARD_FACTOR: 32
# 2**6 (= 64)
BASE_REWARD_FACTOR: 64
# 2**9 (= 512)
WHISTLEBLOWING_REWARD_QUOTIENT: 512
WHISTLEBLOWER_REWARD_QUOTIENT: 512
# 2**3 (= 8)
PROPOSER_REWARD_QUOTIENT: 8
# 2**25 (= 33,554,432)
@ -119,9 +122,12 @@ MAX_TRANSFERS: 0
# Signature domains
# ---------------------------------------------------------------
DOMAIN_BEACON_PROPOSER: 0
DOMAIN_RANDAO: 1
DOMAIN_ATTESTATION: 2
DOMAIN_DEPOSIT: 3
DOMAIN_VOLUNTARY_EXIT: 4
DOMAIN_TRANSFER: 5
DOMAIN_BEACON_PROPOSER: 0x00000000
DOMAIN_RANDAO: 0x01000000
DOMAIN_ATTESTATION: 0x02000000
DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_TRANSFER: 0x05000000
DOMAIN_CUSTODY_BIT_CHALLENGE: 0x06000000
DOMAIN_SHARD_PROPOSER: 0x80000000
DOMAIN_SHARD_ATTESTER: 0x81000000

View File

@ -9,23 +9,24 @@ SHARD_COUNT: 8
# [customized] unsecure, but fast
TARGET_COMMITTEE_SIZE: 4
# 2**12 (= 4,096)
MAX_INDICES_PER_ATTESTATION: 4096
MAX_VALIDATORS_PER_COMMITTEE: 4096
# 2**2 (= 4)
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
CHURN_LIMIT_QUOTIENT: 65536
# Normalizes base rewards
BASE_REWARDS_PER_EPOCH: 5
# [customized] Faster, but unsecure.
SHUFFLE_ROUND_COUNT: 10
# [customized]
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
# Jan 3, 2020
MIN_GENESIS_TIME: 1578009600
# Deposit contract
# ---------------------------------------------------------------
# **TBD**
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890
# 2**5 (= 32)
DEPOSIT_CONTRACT_TREE_DEPTH: 32
# Gwei values
@ -42,20 +43,17 @@ EFFECTIVE_BALANCE_INCREMENT: 1000000000
# Initial values
# ---------------------------------------------------------------
GENESIS_FORK_VERSION: 0x00000000
# 0, GENESIS_EPOCH is derived from this constant
GENESIS_SLOT: 0
# 2**64 - 1
FAR_FUTURE_EPOCH: 18446744073709551615
BLS_WITHDRAWAL_PREFIX: 0
BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters
# ---------------------------------------------------------------
# 6 seconds 6 seconds
SECONDS_PER_SLOT: 6
# [customized] 2 slots
MIN_ATTESTATION_INCLUSION_DELAY: 2
# 2**0 (= 1) slots 6 seconds
MIN_ATTESTATION_INCLUSION_DELAY: 1
# [customized] fast epochs
SLOTS_PER_EPOCH: 8
# 2**0 (= 1) epochs
@ -70,30 +68,32 @@ SLOTS_PER_HISTORICAL_ROOT: 64
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# 2**11 (= 2,048) epochs
PERSISTENT_COMMITTEE_PERIOD: 2048
# 2**6 (= 64) epochs
MAX_EPOCHS_PER_CROSSLINK: 64
# [customized] fast catchup crosslinks
MAX_EPOCHS_PER_CROSSLINK: 4
# 2**2 (= 4) epochs
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
# [customized] 2**12 (= 4,096) epochs
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
# State list lengths
# State vector lengths
# ---------------------------------------------------------------
# [customized] smaller state
LATEST_RANDAO_MIXES_LENGTH: 64
EPOCHS_PER_HISTORICAL_VECTOR: 64
# [customized] smaller state
LATEST_ACTIVE_INDEX_ROOTS_LENGTH: 64
# [customized] smaller state
LATEST_SLASHED_EXIT_LENGTH: 64
EPOCHS_PER_SLASHINGS_VECTOR: 64
# 2**24 (= 16,777,216) historical roots
HISTORICAL_ROOTS_LIMIT: 16777216
# 2**40 (= 1,099,511,627,776) validator spots
VALIDATOR_REGISTRY_LIMIT: 1099511627776
# Reward and penalty quotients
# ---------------------------------------------------------------
# 2**5 (= 32)
BASE_REWARD_FACTOR: 32
# 2**6 (= 64)
BASE_REWARD_FACTOR: 64
# 2**9 (= 512)
WHISTLEBLOWING_REWARD_QUOTIENT: 512
WHISTLEBLOWER_REWARD_QUOTIENT: 512
# 2**3 (= 8)
PROPOSER_REWARD_QUOTIENT: 8
# 2**25 (= 33,554,432)
@ -120,9 +120,12 @@ MAX_TRANSFERS: 0
# Signature domains
# ---------------------------------------------------------------
DOMAIN_BEACON_PROPOSER: 0
DOMAIN_RANDAO: 1
DOMAIN_ATTESTATION: 2
DOMAIN_DEPOSIT: 3
DOMAIN_VOLUNTARY_EXIT: 4
DOMAIN_TRANSFER: 5
DOMAIN_BEACON_PROPOSER: 0x00000000
DOMAIN_RANDAO: 0x01000000
DOMAIN_ATTESTATION: 0x02000000
DOMAIN_DEPOSIT: 0x03000000
DOMAIN_VOLUNTARY_EXIT: 0x04000000
DOMAIN_TRANSFER: 0x05000000
DOMAIN_CUSTODY_BIT_CHALLENGE: 0x06000000
DOMAIN_SHARD_PROPOSER: 0x80000000
DOMAIN_SHARD_ATTESTER: 0x81000000

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
AMOUNT_LENGTH: constant(uint256) = 8 # bytes
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
Deposit: event({
DepositEvent: event({
pubkey: bytes[48],
withdrawal_credentials: bytes[32],
amount: bytes[8],
@ -42,8 +42,9 @@ def to_little_endian_64(value: uint256) -> bytes[8]:
@public
@constant
def get_deposit_root() -> bytes32:
node: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
def get_hash_tree_root() -> bytes32:
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
node: bytes32 = zero_bytes32
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
@ -51,7 +52,7 @@ def get_deposit_root() -> bytes32:
else:
node = sha256(concat(node, self.zero_hashes[height]))
size /= 2
return node
return sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
@public
@ -75,11 +76,11 @@ def deposit(pubkey: bytes[PUBKEY_LENGTH],
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
assert len(signature) == SIGNATURE_LENGTH
# Emit `Deposit` log
# Emit `DepositEvent` log
amount: bytes[8] = self.to_little_endian_64(deposit_amount)
log.Deposit(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
log.DepositEvent(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
# Compute `DepositData` root
# Compute `DepositData` hash tree root
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
signature_root: bytes32 = sha256(concat(
@ -91,7 +92,7 @@ def deposit(pubkey: bytes[PUBKEY_LENGTH],
sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
))
# Add `DepositData` root to Merkle tree (update a single `branch` node)
# Add `DepositData` hash tree root to Merkle tree (update a single `branch` node)
self.deposit_count += 1
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):

View File

@ -1,5 +1,5 @@
eth-tester[py-evm]==0.1.0b39
vyper==0.1.0b9
vyper==0.1.0b10
web3==5.0.0b2
pytest==3.6.1
../test_libs/pyspec

View File

@ -15,26 +15,12 @@ from eth2spec.phase0.spec import (
DepositData,
)
from eth2spec.utils.hash_function import hash
from eth2spec.utils.ssz.ssz_typing import List
from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root,
)
def compute_merkle_root(leaf_nodes):
assert len(leaf_nodes) >= 1
empty_node = b'\x00' * 32
child_nodes = leaf_nodes[:]
for _ in range(DEPOSIT_CONTRACT_TREE_DEPTH):
parent_nodes = []
if len(child_nodes) % 2 == 1:
child_nodes.append(empty_node)
for j in range(0, len(child_nodes), 2):
parent_nodes.append(hash(child_nodes[j] + child_nodes[j + 1]))
child_nodes = parent_nodes
empty_node = hash(empty_node + empty_node)
return child_nodes[0]
@pytest.fixture
def deposit_input():
"""
@ -110,8 +96,8 @@ def test_deposit_inputs(registration_contract,
)
def test_deposit_log(registration_contract, a0, w3, deposit_input):
log_filter = registration_contract.events.Deposit.createFilter(
def test_deposit_event_log(registration_contract, a0, w3, deposit_input):
log_filter = registration_contract.events.DepositEvent.createFilter(
fromBlock='latest',
)
@ -131,13 +117,14 @@ def test_deposit_log(registration_contract, a0, w3, deposit_input):
assert log['signature'] == deposit_input[2]
assert log['index'] == i.to_bytes(8, 'little')
def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input):
log_filter = registration_contract.events.Deposit.createFilter(
log_filter = registration_contract.events.DepositEvent.createFilter(
fromBlock='latest',
)
deposit_amount_list = [randint(MIN_DEPOSIT_AMOUNT, FULL_DEPOSIT_AMOUNT * 2) for _ in range(10)]
leaf_nodes = []
deposit_data_list = []
for i in range(0, 10):
tx_hash = registration_contract.functions.deposit(
*deposit_input,
@ -151,13 +138,12 @@ def test_deposit_tree(registration_contract, w3, assert_tx_failed, deposit_input
assert log["index"] == i.to_bytes(8, 'little')
deposit_data = DepositData(
deposit_data_list.append(DepositData(
pubkey=deposit_input[0],
withdrawal_credentials=deposit_input[1],
amount=deposit_amount_list[i],
signature=deposit_input[2],
)
hash_tree_root_result = hash_tree_root(deposit_data)
leaf_nodes.append(hash_tree_root_result)
root = compute_merkle_root(leaf_nodes)
assert root == registration_contract.functions.get_deposit_root().call()
))
root = hash_tree_root(List[DepositData, 2**32](*deposit_data_list))
assert root == registration_contract.functions.get_hash_tree_root().call()

View File

@ -1,18 +1,18 @@
# Building pyspecs from specs.md
The benefit of the particular spec design is that the given markdown files can be converted to a `spec.py` file for the purposes of testing and linting. The result of this is that bugs are discovered and patched more quickly.
The benefit of the particular spec design is that the given Markdown files can be converted to a `spec.py` file for the purposes of testing and linting. As a result, bugs are discovered and patched more quickly.
Specs can be built from either a single markdown document or multiple files that must be combined in a given order. Given 2 spec objects, `build_spec.combine_spec_objects` will combine them into a single spec object which, subsequently, can be converted into a `specs.py`.
Specs can be built from either a single Markdown document or multiple files that must be combined in a given order. Given 2 spec objects, `build_spec.combine_spec_objects` will combine them into a single spec object which, subsequently, can be converted into a `specs.py`.
## Usage
For usage of the spec builder run `python3 -m build_spec --help`.
For usage of the spec builder, run `python3 -m build_spec --help`.
## `@Labels` and inserts
The functioning of the spec combiner is largely automatic in that given `spec0.md` and `spec1.md`, SSZ Objects will be extended and old functions will be overwritten. Extra functionality is provided for more granular control over how files are combined. In the event that only a small portion of code is to be added to an existing function, insert functionality is provided. This saves having to completely redefine the old function from `spec0.md` in `spec1.md`. This is done by marking where the change is to occur in the old file and marking which code is to be inserted in the new file. This is done as follows:
* In the old file, a label is added as a python comment marking where the code is to be inserted. This would appear as follows in `spec0.md`:
* In the old file, a label is added as a Python comment marking where the code is to be inserted. This would appear as follows in `spec0.md`:
```python
def foo(x):
@ -21,7 +21,7 @@ def foo(x):
return x
```
* In spec1, the new code could then be inserted by having a code-block that looked as follows:
* In spec1, the new code can then be inserted by having a code-block that looks as follows:
```python
#begin insert @YourLabelHere
@ -29,4 +29,4 @@ def foo(x):
#end insert @YourLabelHere
```
**Note** that the code to be inserted has the **same level of indentation** as the surrounding code of its destination insert point.
*Note*: The code to be inserted has the **same level of indentation** as the surrounding code of its destination insert point.

View File

@ -6,17 +6,17 @@ from function_puller import (
from argparse import ArgumentParser
from typing import (
Dict,
List,
Optional,
)
PHASE0_IMPORTS = '''from typing import (
Any,
Dict,
List,
NewType,
Tuple,
Any, Dict, Set, Sequence, Tuple, Optional
)
from dataclasses import (
dataclass,
field,
)
from eth2spec.utils.ssz.ssz_impl import (
@ -24,35 +24,35 @@ from eth2spec.utils.ssz.ssz_impl import (
signing_root,
)
from eth2spec.utils.ssz.ssz_typing import (
# unused: uint8, uint16, uint32, uint128, uint256,
uint64, Container, Vector, BytesN
bit, boolean, Container, List, Vector, uint64,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils.bls import (
bls_aggregate_pubkeys,
bls_verify,
bls_verify_multiple,
bls_sign,
)
# Note: 'int' type defaults to being interpreted as a uint64 by SSZ implementation.
from eth2spec.utils.hash_function import hash
'''
PHASE1_IMPORTS = '''from typing import (
Any,
Dict,
List,
NewType,
Tuple,
Any, Dict, Optional, Set, Sequence, MutableSequence, Tuple,
)
from dataclasses import (
dataclass,
field,
)
from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root,
signing_root,
serialize,
is_empty,
)
from eth2spec.utils.ssz.ssz_typing import (
# unused: uint8, uint16, uint32, uint128, uint256,
uint64, Container, Vector, BytesN
uint64, bit, boolean, Container, List, Vector, Bytes, BytesN,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
)
from eth2spec.utils.bls import (
bls_aggregate_pubkeys,
@ -62,89 +62,101 @@ from eth2spec.utils.bls import (
from eth2spec.utils.hash_function import hash
'''
NEW_TYPES = {
'Slot': 'int',
'Epoch': 'int',
'Shard': 'int',
'ValidatorIndex': 'int',
'Gwei': 'int',
}
BYTE_TYPES = [4, 32, 48, 96]
SUNDRY_FUNCTIONS = '''
def get_ssz_type_by_name(name: str) -> Container:
return globals()[name]
# Monkey patch hash cache
_hash = hash
hash_cache: Dict[bytes, Hash] = {}
def get_eth1_data(distance: uint64) -> Hash:
return hash(distance)
def hash(x: bytes) -> Hash:
if x not in hash_cache:
hash_cache[x] = Hash(_hash(x))
return hash_cache[x]
# Monkey patch validator compute committee code
_compute_committee = compute_committee
committee_cache = {}
committee_cache: Dict[Tuple[Hash, Hash, int, int], Sequence[ValidatorIndex]] = {}
def compute_committee(indices: List[ValidatorIndex], seed: Bytes32, index: int, count: int) -> List[ValidatorIndex]:
param_hash = (hash_tree_root(indices), seed, index, count)
def compute_committee(indices: Sequence[ValidatorIndex], # type: ignore
seed: Hash,
index: int,
count: int) -> Sequence[ValidatorIndex]:
param_hash = (hash(b''.join(index.to_bytes(length=4, byteorder='little') for index in indices)), seed, index, count)
if param_hash in committee_cache:
if param_hash not in committee_cache:
committee_cache[param_hash] = _compute_committee(indices, seed, index, count)
return committee_cache[param_hash]
else:
ret = _compute_committee(indices, seed, index, count)
committee_cache[param_hash] = ret
return ret
# Monkey patch hash cache
_hash = hash
hash_cache = {}
def hash(x):
if x in hash_cache:
return hash_cache[x]
else:
ret = _hash(x)
hash_cache[x] = ret
return ret
# Access to overwrite spec constants based on configuration
def apply_constants_preset(preset: Dict[str, Any]):
def apply_constants_preset(preset: Dict[str, Any]) -> None:
global_vars = globals()
for k, v in preset.items():
if k.startswith('DOMAIN_'):
global_vars[k] = DomainType(v) # domain types are defined as bytes in the configs
else:
global_vars[k] = v
# Deal with derived constants
global_vars['GENESIS_EPOCH'] = slot_to_epoch(GENESIS_SLOT)
global_vars['GENESIS_EPOCH'] = compute_epoch_of_slot(GENESIS_SLOT)
# Initialize SSZ types again, to account for changed lengths
init_SSZ_types()
'''
def strip_comments(raw: str) -> str:
comment_line_regex = re.compile(r'^\s+# ')
lines = raw.split('\n')
out = []
for line in lines:
if not comment_line_regex.match(line):
if ' #' in line:
line = line[:line.index(' #')]
out.append(line)
return '\n'.join(out)
def objects_to_spec(functions: Dict[str, str],
custom_types: Dict[str, str],
constants: Dict[str, str],
ssz_objects: Dict[str, str],
inserts: Dict[str, str],
imports: Dict[str, str],
new_types: Dict[str, str],
byte_types: List[int],
) -> str:
"""
Given all the objects that constitute a spec, combine them into a single pyfile.
"""
new_type_definitions = \
'\n'.join(['''%s = NewType('%s', %s)''' % (key, key, value) for key, value in new_types.items()])
new_type_definitions += '\n' + '\n'.join(['Bytes%s = BytesN[%s]' % (n, n) for n in byte_types])
new_type_definitions = (
'\n\n'.join(
[
f"class {key}({value}):\n pass\n"
for key, value in custom_types.items()
]
)
)
functions_spec = '\n\n'.join(functions.values())
for k in list(constants.keys()):
if k.startswith('DOMAIN_'):
constants[k] = f"DomainType(({constants[k]}).to_bytes(length=4, byteorder='little'))"
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants))
ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values())
ssz_objects_reinitialization_spec = (
'def init_SSZ_types():\n global_vars = globals()\n\n '
+ '\n\n '.join([re.sub(r'(?!\n\n)\n', r'\n ', value[:-1]) for value in ssz_objects.values()])
'def init_SSZ_types() -> None:\n global_vars = globals()\n\n '
+ '\n\n '.join([strip_comments(re.sub(r'(?!\n\n)\n', r'\n ', value[:-1]))
for value in ssz_objects.values()])
+ '\n\n'
+ '\n'.join(map(lambda x: ' global_vars[\'%s\'] = %s' % (x, x), ssz_objects.keys()))
)
spec = (
imports
+ '\n' + new_type_definitions
+ '\n\n' + new_type_definitions
+ '\n\n' + constants_spec
+ '\n\n\n' + ssz_objects_instantiation_spec
+ '\n\n' + functions_spec
@ -170,23 +182,38 @@ def combine_constants(old_constants: Dict[str, str], new_constants: Dict[str, st
return old_constants
def dependency_order_ssz_objects(objects: Dict[str, str]) -> None:
ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN'
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes' # to be removed after updating spec doc
]
def dependency_order_ssz_objects(objects: Dict[str, str], custom_types: Dict[str, str]) -> None:
"""
Determines which SSZ Object is depenedent on which other and orders them appropriately
"""
items = list(objects.items())
for key, value in items:
dependencies = re.findall(r'(: [A-Z][\w[]*)', value)
dependencies = map(lambda x: re.sub(r'\W|Vector|List|Container|uint\d+|Bytes\d+|bytes', '', x), dependencies)
dependencies = []
for line in value.split('\n'):
if not re.match(r'\s+\w+: .+', line):
continue # skip whitespace etc.
line = line[line.index(':') + 1:] # strip of field name
if '#' in line:
line = line[:line.index('#')] # strip of comment
dependencies.extend(re.findall(r'(\w+)', line)) # catch all legible words, potential dependencies
dependencies = filter(lambda x: '_' not in x and x.upper() != x, dependencies) # filter out constants
dependencies = filter(lambda x: x not in ignored_dependencies, dependencies)
dependencies = filter(lambda x: x not in custom_types, dependencies)
for dep in dependencies:
if dep in NEW_TYPES or len(dep) == 0:
continue
key_list = list(objects.keys())
for item in [dep, key] + key_list[key_list.index(dep)+1:]:
objects[item] = objects.pop(item)
def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str]) -> Dict[str, str]:
def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str], custom_types) -> Dict[str, str]:
"""
Takes in old spec and new spec ssz objects, combines them,
and returns the newer versions of the objects in dependency order.
@ -198,7 +225,7 @@ def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str]
# remove leading variable name
value = re.sub(r'^class [\w]*\(Container\):\n', '', value)
old_objects[key] = old_objects.get(key, '') + value
dependency_order_ssz_objects(old_objects)
dependency_order_ssz_objects(old_objects, custom_types)
return old_objects
@ -210,18 +237,25 @@ 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, constants0, ssz_objects0, inserts0 = spec0
functions1, constants1, ssz_objects1, inserts1 = spec1
functions0, custom_types0, constants0, ssz_objects0, inserts0 = spec0
functions1, custom_types1, constants1, ssz_objects1, inserts1 = 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)
ssz_objects = combine_ssz_objects(ssz_objects0, ssz_objects1, custom_types)
inserts = combine_inserts(inserts0, inserts1)
return functions, constants, ssz_objects, inserts
return functions, custom_types, constants, ssz_objects, inserts
def build_phase0_spec(sourcefile: str, outfile: str=None) -> Optional[str]:
functions, constants, ssz_objects, inserts = get_spec(sourcefile)
spec = objects_to_spec(functions, constants, ssz_objects, inserts, PHASE0_IMPORTS, NEW_TYPES, BYTE_TYPES)
def build_phase0_spec(phase0_sourcefile: str, fork_choice_sourcefile: str,
v_guide_sourcefile: str, outfile: str=None) -> Optional[str]:
phase0_spec = get_spec(phase0_sourcefile)
fork_choice_spec = get_spec(fork_choice_sourcefile)
v_guide = get_spec(v_guide_sourcefile)
spec_objects = phase0_spec
for value in [fork_choice_spec, v_guide]:
spec_objects = combine_spec_objects(spec_objects, value)
spec = objects_to_spec(*spec_objects, PHASE0_IMPORTS)
if outfile is not None:
with open(outfile, 'w') as out:
out.write(spec)
@ -231,14 +265,16 @@ def build_phase0_spec(sourcefile: str, outfile: str=None) -> Optional[str]:
def build_phase1_spec(phase0_sourcefile: str,
phase1_custody_sourcefile: str,
phase1_shard_sourcefile: str,
fork_choice_sourcefile: str,
outfile: str=None) -> Optional[str]:
phase0_spec = get_spec(phase0_sourcefile)
phase1_custody = get_spec(phase1_custody_sourcefile)
phase1_shard_data = get_spec(phase1_shard_sourcefile)
fork_choice_spec = get_spec(fork_choice_sourcefile)
spec_objects = phase0_spec
for value in [phase1_custody, phase1_shard_data]:
for value in [phase1_custody, phase1_shard_data, fork_choice_spec]:
spec_objects = combine_spec_objects(spec_objects, value)
spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS, NEW_TYPES, BYTE_TYPES)
spec = objects_to_spec(*spec_objects, PHASE1_IMPORTS)
if outfile is not None:
with open(outfile, 'w') as out:
out.write(spec)
@ -250,13 +286,16 @@ if __name__ == '__main__':
Build the specs from the md docs.
If building phase 0:
1st argument is input spec.md
2nd argument is output spec.py
2nd argument is input fork_choice.md
3rd argument is input validator_guide.md
4th argument is output spec.py
If building phase 1:
1st argument is input spec_phase0.md
2nd argument is input spec_phase1_custody.md
3rd argument is input spec_phase1_shard_data.md
4th argument is output spec.py
4th argument is input fork_choice.md
5th argument is output spec.py
'''
parser = ArgumentParser(description=description)
parser.add_argument("-p", "--phase", dest="phase", type=int, default=0, help="Build for phase #")
@ -264,14 +303,15 @@ If building phase 1:
args = parser.parse_args()
if args.phase == 0:
if len(args.files) == 2:
if len(args.files) == 4:
build_phase0_spec(*args.files)
else:
print(" Phase 0 requires an output as well as an input file.")
print(" Phase 0 requires spec, forkchoice, and v-guide inputs as well as an output file.")
elif args.phase == 1:
if len(args.files) == 4:
if len(args.files) == 5:
build_phase1_spec(*args.files)
else:
print(" Phase 1 requires an output as well as 3 input files (phase0.md and phase1.md, phase1.md)")
print(" Phase 1 requires 4 input files as well as an output file: "
+ "(phase0.md and phase1.md, phase1.md, fork_choice.md, output.py)")
else:
print("Invalid phase: {0}".format(args.phase))

View File

@ -29,6 +29,8 @@ def get_spec(file_name: str) -> SpecObject:
inserts = {}
function_matcher = re.compile(FUNCTION_REGEX)
inserts_matcher = re.compile(BEGIN_INSERT_REGEX)
is_ssz = False
custom_types = {}
for linenum, line in enumerate(open(file_name).readlines()):
line = line.rstrip()
if pulling_from is None and len(line) > 0 and line[0] == '#' and line[-1] == '`':
@ -64,7 +66,7 @@ def get_spec(file_name: str) -> SpecObject:
ssz_objects[current_name] = ssz_objects.get(current_name, '') + line + '\n'
else:
functions[current_name] = functions.get(current_name, '') + line + '\n'
# Handle constant table entries
# Handle constant and custom types table entries
elif pulling_from is None and len(line) > 0 and line[0] == '|':
row = line[1:].split('|')
if len(row) >= 2:
@ -72,12 +74,14 @@ def get_spec(file_name: str) -> SpecObject:
row[i] = row[i].strip().strip('`')
if '`' in row[i]:
row[i] = row[i][:row[i].find('`')]
eligible = True
is_constant_def = True
if row[0][0] not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_':
eligible = False
is_constant_def = False
for c in row[0]:
if c not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789':
eligible = False
if eligible:
is_constant_def = False
if is_constant_def:
constants[row[0]] = row[1].replace('**TBD**', '0x1234567890123456789012345678901234567890')
return functions, constants, ssz_objects, inserts
elif row[1].startswith('uint') or row[1].startswith('Bytes'):
custom_types[row[0]] = row[1]
return functions, custom_types, constants, ssz_objects, inserts

View File

@ -71,10 +71,10 @@ We require:
G2_cofactor = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109
q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
def hash_to_G2(message_hash: Bytes32, domain: uint64) -> [uint384]:
def hash_to_G2(message_hash: Bytes32, domain: Bytes8) -> Tuple[uint384, uint384]:
# Initial candidate x coordinate
x_re = int.from_bytes(hash(message_hash + bytes8(domain) + b'\x01'), 'big')
x_im = int.from_bytes(hash(message_hash + bytes8(domain) + b'\x02'), 'big')
x_re = int.from_bytes(hash(message_hash + domain + b'\x01'), 'big')
x_im = int.from_bytes(hash(message_hash + domain + b'\x02'), 'big')
x_coordinate = Fq2([x_re, x_im]) # x = x_re + i * x_im
# Test candidate y coordinates until a one is found
@ -130,7 +130,7 @@ g = Fq2([g_x, g_y])
### `bls_verify`
Let `bls_verify(pubkey: Bytes48, message_hash: Bytes32, signature: Bytes96, domain: uint64) -> bool`:
Let `bls_verify(pubkey: Bytes48, message_hash: Bytes32, signature: Bytes96, domain: Bytes8) -> bool`:
* Verify that `pubkey` is a valid G1 point.
* Verify that `signature` is a valid G2 point.
@ -138,7 +138,7 @@ Let `bls_verify(pubkey: Bytes48, message_hash: Bytes32, signature: Bytes96, doma
### `bls_verify_multiple`
Let `bls_verify_multiple(pubkeys: List[Bytes48], message_hashes: List[Bytes32], signature: Bytes96, domain: uint64) -> bool`:
Let `bls_verify_multiple(pubkeys: List[Bytes48], message_hashes: List[Bytes32], signature: Bytes96, domain: Bytes8) -> bool`:
* Verify that each `pubkey` in `pubkeys` is a valid G1 point.
* Verify that `signature` is a valid G2 point.

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
- [`deposit` function](#deposit-function)
- [Deposit amount](#deposit-amount)
- [Withdrawal credentials](#withdrawal-credentials)
- [`Deposit` log](#deposit-log)
- [`DepositEvent` log](#depositevent-log)
- [Vyper code](#vyper-code)
<!-- /TOC -->
@ -48,17 +48,17 @@ The amount of ETH (rounded down to the closest Gwei) sent to the deposit contrac
One of the `DepositData` fields is `withdrawal_credentials`. It is a commitment to credentials for withdrawing validator balance (e.g. to another validator, or to shards). The first byte of `withdrawal_credentials` is a version number. As of now, the only expected format is as follows:
* `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE`
* `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX`
* `withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:]` where `withdrawal_pubkey` is a BLS pubkey
The private key corresponding to `withdrawal_pubkey` will be required to initiate a withdrawal. It can be stored separately until a withdrawal is required, e.g. in cold storage.
#### `Deposit` log
#### `DepositEvent` log
Every Ethereum 1.0 deposit emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.
Every Ethereum 1.0 deposit emits a `DepositEvent` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.
## Vyper code
The deposit contract source code, written in Vyper, is available [here](https://github.com/ethereum/eth2.0-specs/blob/dev/deposit_contract/contracts/validator_registration.v.py).
The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.v.py).
*Note*: To save on gas the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof.
*Note*: To save on gas, the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof.

View File

@ -8,102 +8,195 @@
- [Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice](#ethereum-20-phase-0----beacon-chain-fork-choice)
- [Table of contents](#table-of-contents)
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Constants](#constants)
- [Time parameters](#time-parameters)
- [Beacon chain processing](#beacon-chain-processing)
- [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule)
- [Implementation notes](#implementation-notes)
- [Justification and finality at genesis](#justification-and-finality-at-genesis)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [`LatestMessage`](#latestmessage)
- [`Store`](#store)
- [`get_genesis_store`](#get_genesis_store)
- [`get_ancestor`](#get_ancestor)
- [`get_latest_attesting_balance`](#get_latest_attesting_balance)
- [`get_head`](#get_head)
- [Handlers](#handlers)
- [`on_tick`](#on_tick)
- [`on_block`](#on_block)
- [`on_attestation`](#on_attestation)
<!-- /TOC -->
## Introduction
This document represents the specification for the beacon chain fork choice rule, part of Ethereum 2.0 Phase 0.
This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0. It assumes the [beacon chain state transition function spec](./0_beacon-chain.md).
## Prerequisites
## Fork choice
All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](./0_beacon-chain.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout.
The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_genesis_store(genesis_state)` and update `store` by running:
## Constants
- `on_tick(time)` whenever `time > store.time` where `time` is the current Unix time
- `on_block(block)` whenever a block `block` is received
- `on_attestation(attestation)` whenever an attestation `attestation` is received
### Time parameters
*Notes*:
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `SECONDS_PER_SLOT` | `6` | seconds | 6 seconds |
1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time).
2) **Honest clocks**: Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other.
3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](../validator/0_beacon-chain-validator.md) should ensure that `state.latest_eth1_data` of the canonical Ethereum 2.0 chain remains consistent with the canonical Ethereum 1.0 chain. If not, emergency manual intervention will be required.
4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`.
5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost).
## Beacon chain processing
### Helpers
Processing the beacon chain is similar to processing the Ethereum 1.0 chain. Clients download and process blocks and maintain a view of what is the current "canonical chain", terminating at the current "head". For a beacon block, `block`, to be processed by a node, the following conditions must be met:
* The parent block with root `block.parent_root` has been processed and accepted.
* An Ethereum 1.0 block pointed to by the `state.latest_eth1_data.block_hash` has been processed and accepted.
* The node's Unix time is greater than or equal to `state.genesis_time + block.slot * SECONDS_PER_SLOT`.
*Note*: Leap seconds mean that slots will occasionally last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds, possibly several times a year.
*Note*: Nodes needs to have a clock that is roughly (i.e. within `SECONDS_PER_SLOT` seconds) synchronized with the other nodes.
### Beacon chain fork choice rule
The beacon chain fork choice rule is a hybrid that combines justification and finality with Latest Message Driven (LMD) Greediest Heaviest Observed SubTree (GHOST). At any point in time, a validator `v` subjectively calculates the beacon chain head as follows.
* Abstractly define `Store` as the type of storage object for the chain data, and let `store` be the set of attestations and blocks that the validator `v` has observed and verified (in particular, block ancestors must be recursively verified). Attestations not yet included in any chain are still included in `store`.
* Let `finalized_head` be the finalized block with the highest epoch. (A block `B` is finalized if there is a descendant of `B` in `store`, the processing of which sets `B` as finalized.)
* Let `justified_head` be the descendant of `finalized_head` with the highest epoch that has been justified for at least 1 epoch. (A block `B` is justified if there is a descendant of `B` in `store` the processing of which sets `B` as justified.) If no such descendant exists, set `justified_head` to `finalized_head`.
* Let `get_ancestor(store: Store, block: BeaconBlock, slot: Slot) -> BeaconBlock` be the ancestor of `block` with slot number `slot`. The `get_ancestor` function can be defined recursively as:
#### `LatestMessage`
```python
def get_ancestor(store: Store, block: BeaconBlock, slot: Slot) -> BeaconBlock:
"""
Get the ancestor of ``block`` with slot number ``slot``; return ``None`` if not found.
"""
if block.slot == slot:
return block
elif block.slot < slot:
return None
else:
return get_ancestor(store, store.get_parent(block), slot)
@dataclass(eq=True, frozen=True)
class LatestMessage(object):
epoch: Epoch
root: Hash
```
* Let `get_latest_attestation(store: Store, index: ValidatorIndex) -> Attestation` be the attestation with the highest slot number in `store` from the validator with the given `index`. If several such attestations exist, use the one the validator `v` observed first.
* Let `get_latest_attestation_target(store: Store, index: ValidatorIndex) -> BeaconBlock` be the target block in the attestation `get_latest_attestation(store, index)`.
* Let `get_children(store: Store, block: BeaconBlock) -> List[BeaconBlock]` return the child blocks of the given `block`.
* Let `justified_head_state` be the resulting `BeaconState` object from processing the chain up to the `justified_head`.
* The `head` is `lmd_ghost(store, justified_head_state, justified_head)` where the function `lmd_ghost` is defined below. Note that the implementation below is suboptimal; there are implementations that compute the head in time logarithmic in slot count.
#### `Store`
```python
def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) -> BeaconBlock:
"""
Execute the LMD-GHOST algorithm to find the head ``BeaconBlock``.
"""
validators = start_state.validator_registry
active_validator_indices = get_active_validator_indices(validators, slot_to_epoch(start_state.slot))
attestation_targets = [(i, get_latest_attestation_target(store, i)) for i in active_validator_indices]
@dataclass
class Store(object):
time: uint64
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
blocks: Dict[Hash, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Hash, BeaconState] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
```
# Use the rounded-balance-with-hysteresis supplied by the protocol for fork
# choice voting. This reduces the number of recomputations that need to be
# made for optimized implementations that precompute and save data
def get_vote_count(block: BeaconBlock) -> int:
return sum(
start_state.validator_registry[validator_index].effective_balance
for validator_index, target in attestation_targets
if get_ancestor(store, target, block.slot) == block
#### `get_genesis_store`
```python
def get_genesis_store(genesis_state: BeaconState) -> Store:
genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))
root = signing_root(genesis_block)
justified_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
finalized_checkpoint = Checkpoint(epoch=GENESIS_EPOCH, root=root)
return Store(
time=genesis_state.genesis_time,
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
blocks={root: genesis_block},
block_states={root: genesis_state.copy()},
checkpoint_states={justified_checkpoint: genesis_state.copy()},
)
```
head = start_block
while 1:
children = get_children(store, head)
#### `get_ancestor`
```python
def get_ancestor(store: Store, root: Hash, slot: Slot) -> Hash:
block = store.blocks[root]
assert block.slot >= slot
return root if block.slot == slot else get_ancestor(store, block.parent_root, slot)
```
#### `get_latest_attesting_balance`
```python
def get_latest_attesting_balance(store: Store, root: Hash) -> Gwei:
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
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
))
```
#### `get_head`
```python
def get_head(store: Store) -> Hash:
# Execute the LMD-GHOST fork choice
head = store.justified_checkpoint.root
justified_slot = compute_start_slot_of_epoch(store.justified_checkpoint.epoch)
while True:
children = [
root for root in store.blocks.keys()
if store.blocks[root].parent_root == head and store.blocks[root].slot > justified_slot
]
if len(children) == 0:
return head
# Ties broken by favoring block with lexicographically higher root
head = max(children, key=lambda x: (get_vote_count(x), hash_tree_root(x)))
# Sort by latest attesting balance with ties broken lexicographically
head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root))
```
## Implementation notes
### Handlers
### Justification and finality at genesis
#### `on_tick`
During genesis, justification and finality root fields within the `BeaconState` reference `ZERO_HASH` rather than a known block. `ZERO_HASH` in `previous_justified_root`, `current_justified_root`, and `finalized_root` should be considered as an alias to the root of the genesis block.
```python
def on_tick(store: Store, time: uint64) -> None:
store.time = time
```
#### `on_block`
```python
def on_block(store: Store, block: BeaconBlock) -> None:
# Make a copy of the state to avoid mutability issues
assert block.parent_root in store.block_states
pre_state = store.block_states[block.parent_root].copy()
# Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past.
assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT
# Add new block to the store
store.blocks[signing_root(block)] = block
# Check block is a descendant of the finalized block
assert (
get_ancestor(store, signing_root(block), store.blocks[store.finalized_checkpoint.root].slot) ==
store.finalized_checkpoint.root
)
# Check that block is later than the finalized epoch slot
assert block.slot > compute_start_slot_of_epoch(store.finalized_checkpoint.epoch)
# Check the block is valid and compute the post-state
state = state_transition(pre_state, block)
# Add new state for this block to the store
store.block_states[signing_root(block)] = state
# Update justified checkpoint
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
store.justified_checkpoint = state.current_justified_checkpoint
# Update finalized checkpoint
if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch:
store.finalized_checkpoint = state.finalized_checkpoint
```
#### `on_attestation`
```python
def on_attestation(store: Store, attestation: Attestation) -> None:
target = attestation.data.target
# Cannot calculate the current shuffling if have not seen the target
assert target.root in store.blocks
# Attestations cannot be from future epochs. If they are, delay consideration until the epoch arrives
base_state = store.block_states[target.root].copy()
assert store.time >= base_state.genesis_time + compute_start_slot_of_epoch(target.epoch) * SECONDS_PER_SLOT
# Store target checkpoint state if not yet seen
if target not in store.checkpoint_states:
process_slots(base_state, compute_start_slot_of_epoch(target.epoch))
store.checkpoint_states[target] = base_state
target_state = store.checkpoint_states[target]
# Attestations can only affect the fork choice of subsequent slots.
# Delay consideration in the fork choice until their slot is in the past.
attestation_slot = get_attestation_data_slot(target_state, attestation.data)
assert store.time >= (attestation_slot + 1) * SECONDS_PER_SLOT
# Get state at the `target` to validate attestation and calculate the committees
indexed_attestation = get_indexed_attestation(target_state, attestation)
assert is_valid_indexed_attestation(target_state, indexed_attestation)
# Update latest messages
for i in indexed_attestation.custody_bit_0_indices + indexed_attestation.custody_bit_1_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=attestation.data.beacon_block_root)
```

View File

@ -15,7 +15,8 @@
- [Time parameters](#time-parameters)
- [Max operations per block](#max-operations-per-block)
- [Reward and penalty quotients](#reward-and-penalty-quotients)
- [Signature domains](#signature-domains)
- [Signature domain types](#signature-domain-types)
- [TODO PLACEHOLDER](#todo-placeholder)
- [Data structures](#data-structures)
- [Custody objects](#custody-objects)
- [`CustodyChunkChallenge`](#custodychunkchallenge)
@ -33,10 +34,11 @@
- [Helpers](#helpers)
- [`ceillog2`](#ceillog2)
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
- [`get_bit`](#get_bit)
- [`get_custody_chunk_bit`](#get_custody_chunk_bit)
- [`get_chunk_bits_root`](#get_chunk_bits_root)
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
- [`get_validators_custody_reveal_period`](#get_validators_custody_reveal_period)
- [`get_reveal_period`](#get_reveal_period)
- [`replace_empty_or_append`](#replace_empty_or_append)
- [Per-block processing](#per-block-processing)
- [Operations](#operations)
@ -56,18 +58,18 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
## Terminology
* **Custody game**
* **Custody period**
* **Custody chunk**
* **Custody chunk bit**
* **Custody chunk challenge**
* **Custody bit**
* **Custody bit challenge**
* **Custody key**
* **Custody key reveal**
* **Custody key mask**
* **Custody response**
* **Custody response deadline**
- **Custody game**
- **Custody period**
- **Custody chunk**
- **Custody chunk bit**
- **Custody chunk challenge**
- **Custody bit**
- **Custody bit challenge**
- **Custody key**
- **Custody key reveal**
- **Custody key mask**
- **Custody response**
- **Custody response deadline**
## Constants
@ -107,12 +109,20 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| - | - |
| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) |
### Signature domains
### Signature domain types
The following types are defined, mapping into `DomainType` (little endian):
| Name | Value |
| - | - |
| `DOMAIN_CUSTODY_BIT_CHALLENGE` | `6` |
### TODO PLACEHOLDER
| Name | Value |
| - | - |
| `PLACEHOLDER` | `2**32` |
## Data structures
### Custody objects
@ -133,9 +143,9 @@ class CustodyBitChallenge(Container):
responder_index: ValidatorIndex
attestation: Attestation
challenger_index: ValidatorIndex
responder_key: Bytes96
chunk_bits: bytes
signature: Bytes96
responder_key: BLSSignature
chunk_bits: Bytes[PLACEHOLDER]
signature: BLSSignature
```
#### `CustodyChunkChallengeRecord`
@ -146,7 +156,7 @@ class CustodyChunkChallengeRecord(Container):
challenger_index: ValidatorIndex
responder_index: ValidatorIndex
inclusion_epoch: Epoch
data_root: Bytes32
data_root: Hash
depth: uint64
chunk_index: uint64
```
@ -159,10 +169,10 @@ class CustodyBitChallengeRecord(Container):
challenger_index: ValidatorIndex
responder_index: ValidatorIndex
inclusion_epoch: Epoch
data_root: Bytes32
data_root: Hash
chunk_count: uint64
chunk_bits_merkle_root: Bytes32
responder_key: Bytes96
chunk_bits_merkle_root: Hash
responder_key: BLSSignature
```
#### `CustodyResponse`
@ -171,10 +181,10 @@ class CustodyBitChallengeRecord(Container):
class CustodyResponse(Container):
challenge_index: uint64
chunk_index: uint64
chunk: Vector[bytes, BYTES_PER_CUSTODY_CHUNK]
data_branch: List[Bytes32]
chunk_bits_branch: List[Bytes32]
chunk_bits_leaf: Bytes32
chunk: Vector[Bytes[PLACEHOLDER], BYTES_PER_CUSTODY_CHUNK]
data_branch: List[Hash, PLACEHOLDER]
chunk_bits_branch: List[Hash, PLACEHOLDER]
chunk_bits_leaf: Hash
```
### New beacon operations
@ -184,9 +194,9 @@ class CustodyResponse(Container):
```python
class CustodyKeyReveal(Container):
# Index of the validator whose key is being revealed
revealer_index: uint64
revealer_index: ValidatorIndex
# Reveal (masked signature)
reveal: Bytes96
reveal: BLSSignature
```
#### `EarlyDerivedSecretReveal`
@ -196,15 +206,15 @@ Represents an early (punishable) reveal of one of the derived secrets, where der
```python
class EarlyDerivedSecretReveal(Container):
# Index of the validator whose key is being revealed
revealed_index: uint64
revealed_index: ValidatorIndex
# RANDAO epoch of the key that is being revealed
epoch: uint64
epoch: Epoch
# Reveal (masked signature)
reveal: Bytes96
reveal: BLSSignature
# Index of the validator who revealed (whistleblower)
masker_index: uint64
masker_index: ValidatorIndex
# Mask used to hide the actual reveal signature (prevent reveal from being stolen)
mask: Bytes32
mask: Hash
```
### Phase 0 container updates
@ -217,7 +227,7 @@ Add the following fields to the end of the specified container objects. Fields w
class Validator(Container):
# next_custody_reveal_period is initialised to the custody period
# (of the particular validator) in which the validator is activated
# = get_validators_custody_reveal_period(...)
# = get_reveal_period(...)
next_custody_reveal_period: uint64
max_reveal_lateness: uint64
```
@ -226,24 +236,25 @@ class Validator(Container):
```python
class BeaconState(Container):
custody_chunk_challenge_records: List[CustodyChunkChallengeRecord]
custody_bit_challenge_records: List[CustodyBitChallengeRecord]
custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, PLACEHOLDER]
custody_bit_challenge_records: List[CustodyBitChallengeRecord, PLACEHOLDER]
custody_challenge_index: uint64
# Future derived secrets already exposed; contains the indices of the exposed validator
# at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
exposed_derived_secrets: Vector[List[uint64], EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
exposed_derived_secrets: Vector[List[ValidatorIndex, PLACEHOLDER],
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS]
```
#### `BeaconBlockBody`
```python
class BeaconBlockBody(Container):
custody_chunk_challenges: List[CustodyChunkChallenge]
custody_bit_challenges: List[CustodyBitChallenge]
custody_responses: List[CustodyResponse]
custody_key_reveals: List[CustodyKeyReveal]
early_derived_secret_reveals: List[EarlyDerivedSecretReveal]
custody_chunk_challenges: List[CustodyChunkChallenge, PLACEHOLDER]
custody_bit_challenges: List[CustodyBitChallenge, PLACEHOLDER]
custody_responses: List[CustodyResponse, PLACEHOLDER]
custody_key_reveals: List[CustodyKeyReveal, PLACEHOLDER]
early_derived_secret_reveals: List[EarlyDerivedSecretReveal, PLACEHOLDER]
```
## Helpers
@ -251,7 +262,7 @@ class BeaconBlockBody(Container):
### `ceillog2`
```python
def ceillog2(x):
def ceillog2(x: uint64) -> int:
return x.bit_length()
```
@ -264,44 +275,49 @@ def get_custody_chunk_count(crosslink: Crosslink) -> int:
return crosslink_length * chunks_per_epoch
```
### `get_bit`
```python
def get_bit(serialization: bytes, i: uint64) -> int:
"""
Extract the bit in ``serialization`` at position ``i``.
"""
return (serialization[i // 8] >> (i % 8)) % 2
```
### `get_custody_chunk_bit`
```python
def get_custody_chunk_bit(key: Bytes96, chunk: bytes) -> bool:
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
# TODO: Replace with something MPC-friendly, e.g. the Legendre symbol
return get_bitfield_bit(hash(key + chunk), 0)
return bool(get_bit(hash(key + chunk), 0))
```
### `get_chunk_bits_root`
```python
def get_chunk_bits_root(chunk_bitfield: bytes) -> Bytes32:
def get_chunk_bits_root(chunk_bits: bytes) -> Hash:
aggregated_bits = bytearray([0] * 32)
for i in range(0, len(chunk_bitfield), 32):
for i in range(0, len(chunk_bits), 32):
for j in range(32):
aggregated_bits[j] ^= chunk_bitfield[i + j]
aggregated_bits[j] ^= chunk_bits[i + j]
return hash(aggregated_bits)
```
### `get_randao_epoch_for_custody_period`
```python
def get_randao_epoch_for_custody_period(period: int, validator_index: ValidatorIndex) -> Epoch:
def get_randao_epoch_for_custody_period(period: uint64, validator_index: ValidatorIndex) -> Epoch:
next_period_start = (period + 1) * EPOCHS_PER_CUSTODY_PERIOD - validator_index % EPOCHS_PER_CUSTODY_PERIOD
return next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING
return Epoch(next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING)
```
### `get_validators_custody_reveal_period`
### `get_reveal_period`
```python
def get_validators_custody_reveal_period(state: BeaconState,
validator_index: ValidatorIndex,
epoch: Epoch=None) -> int:
def get_reveal_period(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int:
'''
This function returns the reveal period for a given validator.
If no epoch is supplied, the current epoch is assumed.
Note: This function implicitly requires that validators are not removed from the
validator set in fewer than EPOCHS_PER_CUSTODY_PERIOD epochs
Return the reveal period for a given validator.
'''
epoch = get_current_epoch(state) if epoch is None else epoch
return (epoch + validator_index % EPOCHS_PER_CUSTODY_PERIOD) // EPOCHS_PER_CUSTODY_PERIOD
@ -310,7 +326,7 @@ def get_validators_custody_reveal_period(state: BeaconState,
### `replace_empty_or_append`
```python
def replace_empty_or_append(list: List[Any], new_element: Any) -> int:
def replace_empty_or_append(list: MutableSequence[Any], new_element: Any) -> int:
for i in range(len(list)):
if is_empty(list[i]):
list[i] = new_element
@ -332,17 +348,15 @@ Verify that `len(block.body.custody_key_reveals) <= MAX_CUSTODY_KEY_REVEALS`.
For each `reveal` in `block.body.custody_key_reveals`, run the following function:
```python
def process_custody_key_reveal(state: BeaconState,
reveal: CustodyKeyReveal) -> None:
def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> None:
"""
Process ``CustodyKeyReveal`` operation.
Note that this function mutates ``state``.
"""
revealer = state.validator_registry[reveal.revealer_index]
revealer = state.validators[reveal.revealer_index]
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_reveal_period, reveal.revealed_index)
assert revealer.next_custody_reveal_period < get_validators_custody_reveal_period(state, reveal.revealed_index)
assert revealer.next_custody_reveal_period < get_reveal_period(state, reveal.revealed_index)
# Revealed validator is active or exited, but not withdrawn
assert is_slashable_validator(revealer, get_current_epoch(state))
@ -360,11 +374,11 @@ def process_custody_key_reveal(state: BeaconState,
)
# Decrement max reveal lateness if response is timely
if revealer.next_custody_reveal_period == get_validators_custody_reveal_period(state, reveal.revealer_index) - 2:
if revealer.next_custody_reveal_period == get_reveal_period(state, reveal.revealer_index) - 2:
revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT
revealer.max_reveal_lateness = max(
revealer.max_reveal_lateness,
get_validators_custody_reveal_period(state, reveal.revealed_index) - revealer.next_custody_reveal_period
get_reveal_period(state, reveal.revealed_index) - revealer.next_custody_reveal_period
)
# Process reveal
@ -372,7 +386,11 @@ def process_custody_key_reveal(state: BeaconState,
# Reward Block Preposer
proposer_index = get_beacon_proposer_index(state)
increase_balance(state, proposer_index, get_base_reward(state, reveal.revealer_index) // MINOR_REWARD_QUOTIENT)
increase_balance(
state,
proposer_index,
Gwei(get_base_reward(state, reveal.revealer_index) // MINOR_REWARD_QUOTIENT)
)
```
#### Early derived secret reveals
@ -382,24 +400,21 @@ Verify that `len(block.body.early_derived_secret_reveals) <= MAX_EARLY_DERIVED_S
For each `reveal` in `block.body.early_derived_secret_reveals`, run the following function:
```python
def process_early_derived_secret_reveal(state: BeaconState,
reveal: EarlyDerivedSecretReveal) -> None:
def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerivedSecretReveal) -> None:
"""
Process ``EarlyDerivedSecretReveal`` operation.
Note that this function mutates ``state``.
"""
revealed_validator = state.validator_registry[reveal.revealed_index]
masker = state.validator_registry[reveal.masker_index]
revealed_validator = state.validators[reveal.revealed_index]
derived_secret_location = reveal.epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
assert reveal.epoch >= get_current_epoch(state) + RANDAO_PENALTY_EPOCHS
assert reveal.epoch < get_current_epoch(state) + EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
assert revealed_validator.slashed is False
assert not revealed_validator.slashed
assert reveal.revealed_index not in state.exposed_derived_secrets[derived_secret_location]
# Verify signature correctness
masker = state.validator_registry[reveal.masker_index]
masker = state.validators[reveal.masker_index]
pubkeys = [revealed_validator.pubkey, masker.pubkey]
message_hashes = [
hash_tree_root(reveal.epoch),
@ -433,7 +448,7 @@ def process_early_derived_secret_reveal(state: BeaconState,
// len(get_active_validator_indices(state, get_current_epoch(state)))
// PROPOSER_REWARD_QUOTIENT
)
penalty = (
penalty = Gwei(
max_proposer_slot_reward
* EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE
* (len(state.exposed_derived_secrets[derived_secret_location]) + 1)
@ -442,8 +457,8 @@ def process_early_derived_secret_reveal(state: BeaconState,
# Apply penalty
proposer_index = get_beacon_proposer_index(state)
whistleblower_index = reveal.masker_index
whistleblowing_reward = penalty // WHISTLEBLOWING_REWARD_QUOTIENT
proposer_reward = whistleblowing_reward // PROPOSER_REWARD_QUOTIENT
whistleblowing_reward = Gwei(penalty // WHISTLEBLOWER_REWARD_QUOTIENT)
proposer_reward = Gwei(whistleblowing_reward // PROPOSER_REWARD_QUOTIENT)
increase_balance(state, proposer_index, proposer_reward)
increase_balance(state, whistleblower_index, whistleblowing_reward - proposer_reward)
decrease_balance(state, reveal.revealed_index, penalty)
@ -459,16 +474,16 @@ Verify that `len(block.body.custody_chunk_challenges) <= MAX_CUSTODY_CHUNK_CHALL
For each `challenge` in `block.body.custody_chunk_challenges`, run the following function:
```python
def process_chunk_challenge(state: BeaconState,
challenge: CustodyChunkChallenge) -> None:
def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None:
# Verify the attestation
validate_indexed_attestation(state, convert_to_indexed(state, challenge.attestation))
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation))
# Verify it is not too late to challenge
assert slot_to_epoch(challenge.attestation.data.slot) >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
responder = state.validator_registry[challenge.responder_index]
assert (compute_epoch_of_slot(challenge.attestation.data.slot)
>= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY)
responder = state.validators[challenge.responder_index]
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
# Verify the responder participated in the attestation
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bitfield)
attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits)
assert challenge.responder_index in attesters
# Verify the challenge is not a duplicate
for record in state.custody_chunk_challenge_records:
@ -503,60 +518,42 @@ Verify that `len(block.body.custody_bit_challenges) <= MAX_CUSTODY_BIT_CHALLENGE
For each `challenge` in `block.body.custody_bit_challenges`, run the following function:
```python
def process_bit_challenge(state: BeaconState,
challenge: CustodyBitChallenge) -> None:
def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None:
attestation = challenge.attestation
epoch = compute_epoch_of_slot(attestation.data.slot)
shard = attestation.data.crosslink.shard
# Verify challenge signature
challenger = state.validator_registry[challenge.challenger_index]
assert bls_verify(
pubkey=challenger.pubkey,
message_hash=signing_root(challenge),
signature=challenge.signature,
domain=get_domain(state, get_current_epoch(state), DOMAIN_CUSTODY_BIT_CHALLENGE),
)
challenger = state.validators[challenge.challenger_index]
domain = get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state))
assert bls_verify(challenger.pubkey, signing_root(challenge), challenge.signature, domain)
# Verify challenger is slashable
assert is_slashable_validator(challenger, get_current_epoch(state))
# Verify the attestation
attestation = challenge.attestation
validate_indexed_attestation(state, convert_to_indexed(state, attestation))
# Verify the attestation is eligible for challenging
responder = state.validator_registry[challenge.responder_index]
assert (slot_to_epoch(attestation.data.slot) + responder.max_reveal_lateness <=
get_validators_custody_reveal_period(state, challenge.responder_index))
# Verify attestation
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
# Verify attestation is eligible for challenging
responder = state.validators[challenge.responder_index]
assert epoch + responder.max_reveal_lateness <= get_reveal_period(state, challenge.responder_index)
# Verify the responder participated in the attestation
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
assert challenge.responder_index in attesters
# A validator can be the challenger for at most one challenge at a time
# Verifier challenger is not already challenging
for record in state.custody_bit_challenge_records:
assert record.challenger_index != challenge.challenger_index
# Verify the responder is a valid custody key
# Verify the responder custody key
epoch_to_sign = get_randao_epoch_for_custody_period(
get_validators_custody_reveal_period(
state=state,
index=challenge.responder_index,
epoch=slot_to_epoch(attestation.data.slot)),
challenge.responder_index
get_reveal_period(state, challenge.responder_index, epoch),
challenge.responder_index,
)
assert bls_verify(
pubkey=responder.pubkey,
message_hash=hash_tree_root(epoch_to_sign),
signature=challenge.responder_key,
domain=get_domain(
state=state,
domain_type=DOMAIN_RANDAO,
message_epoch=epoch_to_sign,
),
)
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
assert bls_verify(responder.pubkey, hash_tree_root(epoch_to_sign), challenge.responder_key, domain)
# Verify the chunk count
chunk_count = get_custody_chunk_count(attestation.data.crosslink)
assert verify_bitfield(challenge.chunk_bits, chunk_count)
# Verify the first bit of the hash of the chunk bits does not equal the custody bit
custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(challenge.responder_index))
assert custody_bit != get_bitfield_bit(get_chunk_bits_root(challenge.chunk_bits), 0)
committee = get_crosslink_committee(state, epoch, shard)
custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)]
assert custody_bit != get_bit(get_chunk_bits_root(challenge.chunk_bits), 0)
# Add new bit challenge record
new_record = CustodyBitChallengeRecord(
challenge_index=state.custody_challenge_index,
@ -570,7 +567,6 @@ def process_bit_challenge(state: BeaconState,
)
replace_empty_or_append(state.custody_bit_challenge_records, new_record)
state.custody_challenge_index += 1
# Postpone responder withdrawability
responder.withdrawable_epoch = FAR_FUTURE_EPOCH
```
@ -582,8 +578,7 @@ Verify that `len(block.body.custody_responses) <= MAX_CUSTODY_RESPONSES`.
For each `response` in `block.body.custody_responses`, run the following function:
```python
def process_custody_response(state: BeaconState,
response: CustodyResponse) -> None:
def process_custody_response(state: BeaconState, response: CustodyResponse) -> None:
chunk_challenge = next((record for record in state.custody_chunk_challenge_records
if record.challenge_index == response.challenge_index), None)
if chunk_challenge is not None:
@ -604,11 +599,11 @@ def process_chunk_challenge_response(state: BeaconState,
# Verify chunk index
assert response.chunk_index == challenge.chunk_index
# Verify bit challenge data is null
assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == ZERO_HASH
assert response.chunk_bits_branch == [] and response.chunk_bits_leaf == Hash()
# Verify minimum delay
assert get_current_epoch(state) >= challenge.inclusion_epoch + ACTIVATION_EXIT_DELAY
# Verify the chunk matches the crosslink data root
assert verify_merkle_branch(
assert is_valid_merkle_branch(
leaf=hash_tree_root(response.chunk),
branch=response.data_branch,
depth=challenge.depth,
@ -620,7 +615,7 @@ def process_chunk_challenge_response(state: BeaconState,
records[records.index(challenge)] = CustodyChunkChallengeRecord()
# Reward the proposer
proposer_index = get_beacon_proposer_index(state)
increase_balance(state, proposer_index, get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)
increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT))
```
```python
@ -630,10 +625,10 @@ def process_bit_challenge_response(state: BeaconState,
# Verify chunk index
assert response.chunk_index < challenge.chunk_count
# Verify responder has not been slashed
responder = state.validator_registry[challenge.responder_index]
responder = state.validators[challenge.responder_index]
assert not responder.slashed
# Verify the chunk matches the crosslink data root
assert verify_merkle_branch(
assert is_valid_merkle_branch(
leaf=hash_tree_root(response.chunk),
branch=response.data_branch,
depth=ceillog2(challenge.chunk_count),
@ -641,7 +636,7 @@ def process_bit_challenge_response(state: BeaconState,
root=challenge.data_root,
)
# Verify the chunk bit leaf matches the challenge data
assert verify_merkle_branch(
assert is_valid_merkle_branch(
leaf=response.chunk_bits_leaf,
branch=response.chunk_bits_branch,
depth=ceillog2(challenge.chunk_count) >> 8,
@ -650,7 +645,7 @@ def process_bit_challenge_response(state: BeaconState,
)
# Verify the chunk bit does not match the challenge chunk bit
assert (get_custody_chunk_bit(challenge.responder_key, response.chunk)
!= get_bitfield_bit(challenge.chunk_bits_leaf, response.chunk_index % 256))
!= get_bit(challenge.chunk_bits_leaf, response.chunk_index % 256))
# Clear the challenge
records = state.custody_bit_challenge_records
records[records.index(challenge)] = CustodyBitChallengeRecord()
@ -669,10 +664,10 @@ Run `process_reveal_deadlines(state)` immediately after `process_registry_update
process_reveal_deadlines(state)
# end insert @process_reveal_deadlines
def process_reveal_deadlines(state: BeaconState) -> None:
for index, validator in enumerate(state.validator_registry):
for index, validator in enumerate(state.validators):
deadline = validator.next_custody_reveal_period + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
if get_validators_custody_reveal_period(state, index) > deadline:
slash_validator(state, index)
if get_reveal_period(state, ValidatorIndex(index)) > deadline:
slash_validator(state, ValidatorIndex(index))
```
Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadlines(state)`:
@ -682,17 +677,17 @@ Run `process_challenge_deadlines(state)` immediately after `process_reveal_deadl
process_challenge_deadlines(state)
# end insert @process_challenge_deadlines
def process_challenge_deadlines(state: BeaconState) -> None:
for challenge in state.custody_chunk_challenge_records:
if get_current_epoch(state) > challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
slash_validator(state, challenge.responder_index, challenge.challenger_index)
records = state.custody_chunk_challenge_records
records[records.index(challenge)] = CustodyChunkChallengeRecord()
for custody_chunk_challenge in state.custody_chunk_challenge_records:
if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index)
records = state.custody_chunk_challenge
records[records.index(custody_chunk_challenge)] = CustodyChunkChallengeRecord()
for challenge in state.custody_bit_challenge_records:
if get_current_epoch(state) > challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
slash_validator(state, challenge.responder_index, challenge.challenger_index)
for custody_bit_challenge in state.custody_bit_challenge_records:
if get_current_epoch(state) > custody_bit_challenge.inclusion_epoch + CUSTODY_RESPONSE_DEADLINE:
slash_validator(state, custody_bit_challenge.responder_index, custody_bit_challenge.challenger_index)
records = state.custody_bit_challenge_records
records[records.index(challenge)] = CustodyBitChallengeRecord()
records[records.index(custody_bit_challenge)] = CustodyBitChallengeRecord()
```
Append this to `process_final_updates(state)`:
@ -710,8 +705,8 @@ def after_process_final_updates(state: BeaconState) -> None:
validator_indices_in_records = set(
[record.challenger_index for record in records] + [record.responder_index for record in records]
)
for index, validator in enumerate(state.validator_registry):
for index, validator in enumerate(state.validators):
if index not in validator_indices_in_records:
if validator.exit_epoch != FAR_FUTURE_EPOCH and validator.withdrawable_epoch == FAR_FUTURE_EPOCH:
validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
```

View File

@ -9,26 +9,33 @@
- [Ethereum 2.0 Phase 1 -- Shard Data Chains](#ethereum-20-phase-1----shard-data-chains)
- [Table of contents](#table-of-contents)
- [Introduction](#introduction)
- [Constants](#constants)
- [Custom types](#custom-types)
- [Configuration](#configuration)
- [Misc](#misc)
- [Initial values](#initial-values)
- [Time parameters](#time-parameters)
- [Signature domains](#signature-domains)
- [Signature domain types](#signature-domain-types)
- [TODO PLACEHOLDER](#todo-placeholder)
- [Data structures](#data-structures)
- [`ShardBlockBody`](#shardblockbody)
- [`ShardAttestation`](#shardattestation)
- [`ShardBlock`](#shardblock)
- [`ShardBlockHeader`](#shardblockheader)
- [`ShardBlock`](#shardblock)
- [`ShardBlockSignatures`](#shardblocksignatures)
- [`ShardBlockCore`](#shardblockcore)
- [`ExtendedShardBlockCore`](#extendedshardblockcore)
- [Helper functions](#helper-functions)
- [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot)
- [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot)
- [`get_shard_period_start_epoch`](#get_shard_period_start_epoch)
- [`get_period_committee`](#get_period_committee)
- [`get_switchover_epoch`](#get_switchover_epoch)
- [`get_persistent_committee`](#get_persistent_committee)
- [`get_shard_proposer_index`](#get_shard_proposer_index)
- [`get_shard_block_proposer_index`](#get_shard_block_proposer_index)
- [`get_shard_block_attester_committee`](#get_shard_block_attester_committee)
- [`get_shard_header`](#get_shard_header)
- [`verify_shard_attestation_signature`](#verify_shard_attestation_signature)
- [`pad`](#pad)
- [`flatten_shard_header`](#flatten_shard_header)
- [`compute_crosslink_data_root`](#compute_crosslink_data_root)
- [Object validity](#object-validity)
- [Shard blocks](#shard-blocks)
- [Shard attestations](#shard-attestations)
- [Beacon attestations](#beacon-attestations)
- [Shard fork choice rule](#shard-fork-choice-rule)
@ -38,14 +45,29 @@
This document describes the shard data layer and the shard fork choice rule in Phase 1 of Ethereum 2.0.
## Constants
## Custom types
We define the following Python custom types for type hinting and readability:
| Name | SSZ equivalent | Description |
| - | - | - |
| `ShardSlot` | `uint64` | a slot number in shard chain |
## Configuration
### Misc
| Name | Value |
| - | - |
| `BYTES_PER_SHARD_BLOCK_BODY` | `2**14` (= 16,384) |
| `MAX_SHARD_ATTESTIONS` | `2**4` (= 16) |
| `SHARD_HEADER_SIZE` | `2**9` (= 512) |
| `SHARD_BLOCK_SIZE_LIMIT` | `2**16` (= 65,536) |
| `SHARD_SLOTS_PER_BEACON_SLOT` | `2**1` (= 2) |
| `MAX_PERSISTENT_COMMITTEE_SIZE` | `2**7` (= 128) |
### Initial values
| Name | Value |
| - | - |
| `PHASE_1_FORK_EPOCH` | **TBD** |
| `PHASE_1_FORK_SLOT` | **TBD** |
| `GENESIS_SHARD_SLOT` | 0 |
@ -54,94 +76,118 @@ This document describes the shard data layer and the shard fork choice rule in P
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.2 minutes |
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | ~9 days |
| `SECONDS_PER_SLOT` | `2**1 * 3**1` (= 6) | 6 seconds |
| `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.4 minutes |
| `EPOCHS_PER_SHARD_PERIOD` | `2**8` (= 256) | epochs | ~27 hours |
### Signature domains
### Signature domain types
The following types are defined, mapping into `DomainType` (little endian):
| Name | Value |
| - | - |
| `DOMAIN_SHARD_PROPOSER` | `128` |
| `DOMAIN_SHARD_ATTESTER` | `129` |
### TODO PLACEHOLDER
| Name | Value |
| - | - |
| `PLACEHOLDER` | `2**3` |
## Data structures
### `ShardBlockBody`
_Note: the shard block header structure is carefully designed so that all of the values have the same depth in a hash tree implementation, so `hash_tree_root(SSZ_partial(x)) == hash_tree_root(x)` (using the "left-to-right leaves" scheme [here](https://github.com/ethereum/eth2.0-specs/issues/1303)), which allows shard block headers to look like an SSZ object when in the crosslink structure. This is done by balancing it so that 7 or 8 items are on the left side (the "core") and two 96-byte (ie. 3*2 = 6 chunk) items are on the right side. Change with care._
### `ShardBlockHeader`
```python
class ShardBlockBody(Container):
data: Vector[bytes, BYTES_PER_SHARD_BLOCK_BODY]
```
### `ShardAttestation`
```python
class ShardAttestation(Container):
class data(Container):
slot: uint64
shard: uint64
shard_block_root: Bytes32
aggregation_bitfield: bytes
aggregate_signature: Bytes96
class ShardBlockHeader(Container):
core: ShardBlockCore
signatures: ShardBlockSignatures
```
### `ShardBlock`
```python
class ShardBlock(Container):
slot: uint64
shard: uint64
beacon_chain_root: Bytes32
parent_root: Bytes32
data: ShardBlockBody
state_root: Bytes32
attestations: List[ShardAttestation]
signature: Bytes96
core: ExtendedShardBlockCore
signatures: ShardBlockSignatures
```
### `ShardBlockHeader`
### `ShardBlockSignatures`
```python
class ShardBlockHeader(Container):
slot: uint64
shard: uint64
beacon_chain_root: Bytes32
parent_root: Bytes32
body_root: Bytes32
state_root: Bytes32
attestations: List[ShardAttestation]
signature: Bytes96
class ShardBlockSignatures(Container):
attestation_signature: BLSSignature
proposer_signature: BLSSignature
```
### `ShardBlockCore`
```python
class ShardBlockCore(Container):
slot: ShardSlot
beacon_chain_root: Hash
parent_root: Hash
data_root: Hash
state_root: Hash
total_bytes: uint64
attester_bitfield: Bitvector[MAX_PERSISTENT_COMMITTEE_SIZE * 2]
```
### `ExtendedShardBlockCore`
```python
class ExtendedShardBlockCore(Container):
slot: ShardSlot
beacon_chain_root: Hash
parent_root: Hash
data: Bytes[SHARD_BLOCK_SIZE_LIMIT - SHARD_HEADER_SIZE]
state_root: Hash
total_bytes: uint64
attester_bitfield: Bitvector[MAX_PERSISTENT_COMMITTEE_SIZE * 2]
```
## Helper functions
### `compute_slot_of_shard_slot`
```python
def compute_slot_of_shard_slot(slot: ShardSlot) -> Epoch:
return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT)
```
### `compute_epoch_of_shard_slot`
```python
def compute_epoch_of_shard_slot(slot: ShardSlot) -> Epoch:
return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT // SLOTS_PER_EPOCH)
```
### `get_shard_period_start_epoch`
```python
def get_shard_period_start_epoch(epoch: Epoch, lookback: Epoch=Epoch(0)) -> Epoch:
return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD)
```
### `get_period_committee`
```python
def get_period_committee(state: BeaconState,
epoch: Epoch,
shard: Shard,
index: int,
count: int) -> List[ValidatorIndex]:
shard: Shard) -> List[ValidatorIndex, MAX_PERSISTENT_COMMITTEE_SIZE]:
"""
Return committee for a period. Used to construct persistent committees.
"""
return compute_committee(
full_committee = compute_committee(
indices=get_active_validator_indices(state, epoch),
seed=generate_seed(state, epoch),
index=shard * count + index,
count=SHARD_COUNT * count,
seed=get_seed(state, epoch),
index=shard,
count=SHARD_COUNT,
)
```
### `get_switchover_epoch`
```python
def get_switchover_epoch(state: BeaconState, epoch: Epoch, index: ValidatorIndex):
earlier_start_epoch = epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD * 2
return (bytes_to_int(hash(generate_seed(state, earlier_start_epoch) + int_to_bytes(index, length=3)[0:8]))
% PERSISTENT_COMMITTEE_PERIOD)
return full_committee[:MAX_PERSISTENT_COMMITTEE_SIZE]
```
### `get_persistent_committee`
@ -149,52 +195,47 @@ def get_switchover_epoch(state: BeaconState, epoch: Epoch, index: ValidatorIndex
```python
def get_persistent_committee(state: BeaconState,
shard: Shard,
slot: Slot) -> List[ValidatorIndex]:
slot: ShardSlot) -> Sequence[ValidatorIndex]:
"""
Return the persistent committee for the given ``shard`` at the given ``slot``.
"""
epoch = slot_to_epoch(slot)
earlier_start_epoch = epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD * 2
later_start_epoch = epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD
epoch = compute_epoch_of_shard_slot(slot)
committee_count = max(
len(get_active_validator_indices(state.validator_registry, earlier_start_epoch)) //
(SHARD_COUNT * TARGET_COMMITTEE_SIZE),
len(get_active_validator_indices(state.validator_registry, later_start_epoch)) //
(SHARD_COUNT * TARGET_COMMITTEE_SIZE),
) + 1
index = slot % committee_count
earlier_committee = get_period_committee(state, shard, earlier_start_epoch, index, committee_count)
later_committee = get_period_committee(state, shard, later_start_epoch, index, committee_count)
earlier_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=Epoch(2)), shard)
later_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=Epoch(1)), shard)
# Take not-yet-cycled-out validators from earlier committee and already-cycled-in validators from
# later committee; return a sorted list of the union of the two, deduplicated
return sorted(list(set(
[i for i in earlier_committee if epoch % PERSISTENT_COMMITTEE_PERIOD < get_switchover_epoch(state, epoch, i)] +
[i for i in later_committee if epoch % PERSISTENT_COMMITTEE_PERIOD >= get_switchover_epoch(state, epoch, i)]
)))
return sorted(set(
[i for i in earlier_committee if epoch % EPOCHS_PER_SHARD_PERIOD < i % EPOCHS_PER_SHARD_PERIOD]
+ [i for i in later_committee if epoch % EPOCHS_PER_SHARD_PERIOD >= i % EPOCHS_PER_SHARD_PERIOD]
))
```
### `get_shard_proposer_index`
### `get_shard_block_proposer_index`
```python
def get_shard_proposer_index(state: BeaconState,
def get_shard_block_proposer_index(state: BeaconState,
shard: Shard,
slot: Slot) -> ValidatorIndex:
slot: ShardSlot) -> Optional[ValidatorIndex]:
# Randomly shift persistent committee
persistent_committee = get_persistent_committee(state, shard, slot)
seed = hash(state.current_shuffling_seed + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8))
random_index = bytes_to_int(seed[0:8]) % len(persistent_committee)
persistent_committee = persistent_committee[random_index:] + persistent_committee[:random_index]
persistent_committee = list(get_persistent_committee(state, shard, slot))
current_epoch = get_current_epoch(state)
# Search for an active proposer
for index in persistent_committee:
if is_active_validator(state.validator_registry[index], get_current_epoch(state)):
return index
# No block can be proposed if no validator is active
active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], current_epoch)]
if not any(active_indices):
return None
MAX_RANDOM_BYTE = 2**8 - 1
seed = hash(get_seed(state, current_epoch) + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8))
i = 0
while True:
candidate_index = active_indices[(slot + i) % len(active_indices)]
random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32]
effective_balance = state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
return ValidatorIndex(candidate_index)
i += 1
```
### `get_shard_header`
@ -202,67 +243,60 @@ def get_shard_proposer_index(state: BeaconState,
```python
def get_shard_header(block: ShardBlock) -> ShardBlockHeader:
return ShardBlockHeader(
slot=block.slot,
shard=block.shard,
beacon_chain_root=block.beacon_chain_root,
parent_root=block.parent_root,
body_root=hash_tree_root(block.body),
state_root=block.state_root,
attestations=block.attestations,
signature=block.signature,
core=ShardBlockCore(
slot=block.core.slot,
beacon_chain_root=block.core.beacon_chain_root,
parent_root=block.core.parent_root,
data_root=hash_tree_root(block.core.data),
state_root=block.core.state_root,
total_bytes=block.core.total_bytes,
attester_bitfield=block.core.attester_bitfield
),
signatures=block.signatures
)
```
### `verify_shard_attestation_signature`
### `pad`
```python
def verify_shard_attestation_signature(state: BeaconState,
attestation: ShardAttestation) -> None:
data = attestation.data
persistent_committee = get_persistent_committee(state, data.shard, data.slot)
assert verify_bitfield(attestation.aggregation_bitfield, len(persistent_committee))
pubkeys = []
for i, index in enumerate(persistent_committee):
if get_bitfield_bit(attestation.aggregation_bitfield, i) == 0b1:
validator = state.validator_registry[index]
assert is_active_validator(validator, get_current_epoch(state))
pubkeys.append(validator.pubkey)
assert bls_verify(
pubkey=bls_aggregate_pubkeys(pubkeys),
message_hash=data.shard_block_root,
signature=attestation.aggregate_signature,
domain=get_domain(state, slot_to_epoch(data.slot), DOMAIN_SHARD_ATTESTER)
def pad(x: bytes, length: int) -> bytes:
assert len(x) <= length
return x + b'\x00' * (length - len(x))
```
### `flatten_shard_header`
```python
def flatten_shard_header(header: ShardBlockHeader) -> Bytes[SHARD_HEADER_SIZE]:
"""
Converts a shard block header into a flat object with the same hash tree root. Used
in the crosslink construction.
"""
committee_size = len(header.core.attester_bitfield)
attester_bits = [header.core.attester_bitfield[i] if i < committee_size else 0 for i in range(256)]
attester_bytes = bytes([sum([attester_bits[i + j] << j for j in range(8)]) for i in range(0, 256, 8)])
return (
pad(int_to_bytes(header.core.slot, length=8), 32) +
header.core.beacon_chain_root +
header.core.parent_root +
header.core.data_root +
header.core.state_root +
pad(int_to_bytes(header.core.total_bytes, length=8), 32) +
attester_bytes +
b'\x00' * 32 +
pad(header.signatures.attestation_signature, 128) +
pad(header.signatures.proposer_signature, 128)
)
```
### `compute_crosslink_data_root`
```python
def compute_crosslink_data_root(blocks: List[ShardBlock]) -> Bytes32:
def is_power_of_two(value: int) -> bool:
return (value > 0) and (value & (value - 1) == 0)
def pad_to_power_of_2(values: List[bytes]) -> List[bytes]:
while not is_power_of_two(len(values)):
values += [b'\x00' * BYTES_PER_SHARD_BLOCK_BODY]
return values
def hash_tree_root_of_bytes(data: bytes) -> bytes:
return hash_tree_root([data[i:i + 32] for i in range(0, len(data), 32)])
def zpad(data: bytes, length: int) -> bytes:
return data + b'\x00' * (length - len(data))
return hash(
hash_tree_root(pad_to_power_of_2([
hash_tree_root_of_bytes(
zpad(serialize(get_shard_header(block)), BYTES_PER_SHARD_BLOCK_BODY)
) for block in blocks
]))
+ hash_tree_root(pad_to_power_of_2([
hash_tree_root_of_bytes(block.body) for block in blocks
]))
)
def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash:
header = b''.join([flatten_shard_header(get_shard_header(block)) for block in blocks])
footer = b''.join([block.core.data for block in blocks])
MAX_SIZE = SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
return hash_tree_root(BytesN[MAX_SIZE](pad(header + footer, MAX_SIZE)))
```
## Object validity
@ -271,15 +305,17 @@ def compute_crosslink_data_root(blocks: List[ShardBlock]) -> Bytes32:
Let:
* `beacon_blocks` be the `BeaconBlock` list such that `beacon_blocks[slot]` is the canonical `BeaconBlock` at slot `slot`
* `beacon_state` be the canonical `BeaconState` after processing `beacon_blocks[-1]`
* `valid_shard_blocks` be the list of valid `ShardBlock`, recursively defined
* `candidate` be a candidate `ShardBlock` for which validity is to be determined by running `is_valid_shard_block`
- `beacon_blocks` be the `BeaconBlock` list such that `beacon_blocks[slot]` is the canonical `BeaconBlock` at slot `slot`
- `beacon_state` be the canonical `BeaconState` after processing `beacon_blocks[-1]`
- `shard` is the shard ID
- `valid_shard_blocks` be the list of valid `ShardBlock`, recursively defined
- `candidate` be a candidate `ShardBlock` for which validity is to be determined by running `is_valid_shard_block`
```python
def is_valid_shard_block(beacon_blocks: List[BeaconBlock],
beacon_state: BeaconState,
valid_shard_blocks: List[ShardBlock],
def is_valid_shard_block(beacon_state: BeaconState,
beacon_blocks: Sequence[BeaconBlock],
shard: Shard,
valid_shard_blocks: Sequence[ShardBlock],
candidate: ShardBlock) -> bool:
# Check if block is already determined valid
for _, block in enumerate(valid_shard_blocks):
@ -287,116 +323,93 @@ def is_valid_shard_block(beacon_blocks: List[BeaconBlock],
return True
# Check slot number
assert candidate.slot >= PHASE_1_FORK_SLOT
# Check shard number
assert candidate.shard <= SHARD_COUNT
assert compute_slot_of_shard_slot(candidate.core.slot) >= PHASE_1_FORK_SLOT
# Check beacon block
beacon_block = beacon_blocks[candidate.slot]
assert candidate.beacon_block_root == signing_root(beacon_block)
assert beacon_block.slot <= candidate.slot
beacon_block_slot = compute_start_slot_of_epoch(compute_epoch_of_shard_slot(candidate.core.slot))
beacon_block = beacon_blocks[beacon_block_slot]
assert candidate.core.beacon_block_root == signing_root(beacon_block)
assert beacon_block.slot <= candidate.core.slot
# Check state root
assert candidate.state_root == ZERO_HASH # [to be removed in phase 2]
assert candidate.core.state_root == Hash() # [to be removed in phase 2]
# Check parent block
if candidate.slot == PHASE_1_FORK_SLOT:
assert candidate.parent_root == ZERO_HASH
else:
if candidate.core.parent_root != Hash():
parent_block = next(
(block for block in valid_shard_blocks if signing_root(block) == candidate.parent_root),
(block for block in valid_shard_blocks if hash_tree_root(block.core) == candidate.core.parent_root),
None
)
assert parent_block is not None
assert parent_block.shard == candidate.shard
assert parent_block.slot < candidate.slot
assert signing_root(beacon_blocks[parent_block.slot]) == parent_block.beacon_chain_root
assert parent_block.core.slot < candidate.core.slot
parent_beacon_block_slot = compute_start_slot_of_epoch(compute_epoch_of_shard_slot(parent_block.core.slot))
assert signing_root(beacon_blocks[parent_beacon_block_slot]) == parent_block.core.beacon_chain_root
# Check attestations
assert len(candidate.attestations) <= MAX_SHARD_ATTESTIONS
for _, attestation in enumerate(candidate.attestations):
assert max(GENESIS_SHARD_SLOT, candidate.slot - SLOTS_PER_EPOCH) <= attestation.data.slot
assert attestation.data.slot <= candidate.slot - MIN_ATTESTATION_INCLUSION_DELAY
assert attestation.data.crosslink.shard == candidate.shard
verify_shard_attestation_signature(beacon_state, attestation)
attester_committee = get_persistent_committee(beacon_state, shard, block.core.slot)
pubkeys = []
for i, index in enumerate(attester_committee):
if block.core.attester_bitfield[i]:
pubkeys.append(beacon_state.validators[index].pubkey)
for i in range(len(attester_committee), MAX_PERSISTENT_COMMITTEE_SIZE * 2):
assert block.attester_bitfield[i] is False
assert bls_verify(
pubkey=bls_aggregate_pubkeys(pubkeys),
message_hash=candidate.core.parent_root,
signature=candidate.signatures.attestation_signature,
domain=get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(candidate.core.slot))
)
# Check signature
proposer_index = get_shard_proposer_index(beacon_state, candidate.shard, candidate.slot)
# Check proposer
proposer_index = get_shard_block_proposer_index(beacon_state, shard, candidate.core.slot)
assert proposer_index is not None
assert bls_verify(
pubkey=beacon_state.validator_registry[proposer_index].pubkey,
message_hash=signing_root(block),
signature=candidate.signature,
domain=get_domain(beacon_state, slot_to_epoch(candidate.slot), DOMAIN_SHARD_PROPOSER),
pubkey=beacon_state.validators[proposer_index].pubkey,
message_hash=hash_tree_root(candidate.core),
signature=candidate.signatures.proposer_signature,
domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(candidate.core.slot)),
)
return True
```
### Shard attestations
Let:
* `valid_shard_blocks` be the list of valid `ShardBlock`
* `beacon_state` be the canonical `BeaconState`
* `candidate` be a candidate `ShardAttestation` for which validity is to be determined by running `is_valid_shard_attestation`
```python
def is_valid_shard_attestation(valid_shard_blocks: List[ShardBlock],
beacon_state: BeaconState,
candidate: ShardAttestation) -> bool:
# Check shard block
shard_block = next(
(block for block in valid_shard_blocks if signing_root(block) == candidate.data.shard_block_root),
None,
)
assert shard_block is not None
assert shard_block.slot == candidate.data.slot
assert shard_block.shard == candidate.data.shard
# Check signature
verify_shard_attestation_signature(beacon_state, candidate)
return True
```
### Beacon attestations
Let:
* `shard` be a valid `Shard`
* `shard_blocks` be the `ShardBlock` list such that `shard_blocks[slot]` is the canonical `ShardBlock` for shard `shard` at slot `slot`
* `beacon_state` be the canonical `BeaconState`
* `valid_attestations` be the list of valid `Attestation`, recursively defined
* `candidate` be a candidate `Attestation` which is valid under Phase 0 rules, and for which validity is to be determined under Phase 1 rules by running `is_valid_beacon_attestation`
- `shard` be a valid `Shard`
- `shard_blocks` be the `ShardBlock` list such that `shard_blocks[slot]` is the canonical `ShardBlock` for shard `shard` at slot `slot`
- `beacon_state` be the canonical `BeaconState`
- `valid_attestations` be the set of valid `Attestation` objects, recursively defined
- `candidate` be a candidate `Attestation` which is valid under Phase 0 rules, and for which validity is to be determined under Phase 1 rules by running `is_valid_beacon_attestation`
```python
def is_valid_beacon_attestation(shard: Shard,
shard_blocks: List[ShardBlock],
shard_blocks: Sequence[ShardBlock],
beacon_state: BeaconState,
valid_attestations: List[Attestation],
valid_attestations: Set[Attestation],
candidate: Attestation) -> bool:
# Check if attestation is already determined valid
for _, attestation in enumerate(valid_attestations):
for attestation in valid_attestations:
if candidate == attestation:
return True
# Check previous attestation
if candidate.data.previous_crosslink.epoch <= PHASE_1_FORK_EPOCH:
assert candidate.data.previous_crosslink.data_root == ZERO_HASH
assert candidate.data.previous_crosslink.data_root == Hash()
else:
previous_attestation = next(
(attestation for attestation in valid_attestations if
attestation.data.crosslink.data_root == candidate.data.previous_crosslink.data_root),
(attestation for attestation in valid_attestations
if attestation.data.crosslink.data_root == candidate.data.previous_crosslink.data_root),
None,
)
assert previous_attestation is not None
assert candidate.data.previous_attestation.epoch < slot_to_epoch(candidate.data.slot)
assert candidate.data.previous_attestation.epoch < compute_epoch_of_slot(candidate.data.slot)
# Check crosslink data root
start_epoch = beacon_state.latest_crosslinks[shard].epoch
end_epoch = min(slot_to_epoch(candidate.data.slot) - CROSSLINK_LOOKBACK, start_epoch + MAX_EPOCHS_PER_CROSSLINK)
start_epoch = beacon_state.crosslinks[shard].epoch
end_epoch = min(compute_epoch_of_slot(candidate.data.slot) - CROSSLINK_LOOKBACK,
start_epoch + MAX_EPOCHS_PER_CROSSLINK)
blocks = []
for slot in range(start_epoch * SLOTS_PER_EPOCH, end_epoch * SLOTS_PER_EPOCH):
blocks.append(shard_blocks[slot])

View File

@ -31,7 +31,7 @@ We define an "expansion" of an object as an object where a field in an object th
We define two expansions:
* `ExtendedBeaconState`, which is identical to a `BeaconState` except `latest_active_index_roots: List[Bytes32]` is replaced by `latest_active_indices: List[List[ValidatorIndex]]`, where `BeaconState.latest_active_index_roots[i] = hash_tree_root(ExtendedBeaconState.latest_active_indices[i])`.
* `ExtendedBeaconState`, which is identical to a `BeaconState` except `compact_committees_roots: List[Bytes32]` is replaced by `active_indices: List[List[ValidatorIndex]]`, where `BeaconState.compact_committees_roots[i] = hash_tree_root(ExtendedBeaconState.active_indices[i])`.
* `ExtendedBeaconBlock`, which is identical to a `BeaconBlock` except `state_root` is replaced with the corresponding `state: ExtendedBeaconState`.
### `get_active_validator_indices`
@ -40,10 +40,10 @@ Note that there is now a new way to compute `get_active_validator_indices`:
```python
def get_active_validator_indices(state: ExtendedBeaconState, epoch: Epoch) -> List[ValidatorIndex]:
return state.latest_active_indices[epoch % LATEST_ACTIVE_INDEX_ROOTS_LENGTH]
return state.active_indices[epoch % EPOCHS_PER_HISTORICAL_VECTOR]
```
Note that it takes `state` instead of `state.validator_registry` as an argument. This does not affect its use in `get_shuffled_committee`, because `get_shuffled_committee` has access to the full `state` as one of its arguments.
Note that it takes `state` instead of `state.validators` as an argument. This does not affect its use in `get_shuffled_committee`, because `get_shuffled_committee` has access to the full `state` as one of its arguments.
### `MerklePartial`
@ -84,8 +84,8 @@ def get_period_data(block: ExtendedBeaconBlock, shard_id: Shard, later: bool) ->
indices = get_period_committee(block.state, shard_id, period_start, 0, committee_count)
return PeriodData(
validator_count,
generate_seed(block.state, period_start),
[block.state.validator_registry[i] for i in indices],
get_seed(block.state, period_start),
[block.state.validators[i] for i in indices],
)
```
@ -124,7 +124,7 @@ def compute_committee(header: BeaconBlockHeader,
maximal_later_committee = validator_memory.later_period_data.committee
earlier_start_epoch = get_earlier_start_epoch(header.slot)
later_start_epoch = get_later_start_epoch(header.slot)
epoch = slot_to_epoch(header.slot)
epoch = compute_epoch_of_slot(header.slot)
committee_count = max(
earlier_validator_count // (SHARD_COUNT * TARGET_COMMITTEE_SIZE),
@ -153,8 +153,8 @@ def compute_committee(header: BeaconBlockHeader,
# Take not-yet-cycled-out validators from earlier committee and already-cycled-in validators from
# later committee; return a sorted list of the union of the two, deduplicated
return sorted(list(set(
[i for i in actual_earlier_committee if epoch % PERSISTENT_COMMITTEE_PERIOD < get_switchover_epoch(i)] +
[i for i in actual_later_committee if epoch % PERSISTENT_COMMITTEE_PERIOD >= get_switchover_epoch(i)]
[i for i in actual_earlier_committee if epoch % PERSISTENT_COMMITTEE_PERIOD < get_switchover_epoch(i)]
+ [i for i in actual_later_committee if epoch % PERSISTENT_COMMITTEE_PERIOD >= get_switchover_epoch(i)]
)))
```
@ -167,8 +167,8 @@ If a client wants to update its `finalized_header` it asks the network for a `Bl
```python
{
'header': BeaconBlockHeader,
'shard_aggregate_signature': 'bytes96',
'shard_bitfield': 'bytes',
'shard_aggregate_signature': BLSSignature,
'shard_bits': Bitlist[PLACEHOLDER],
'shard_parent_block': ShardBlock,
}
```
@ -180,20 +180,20 @@ def verify_block_validity_proof(proof: BlockValidityProof, validator_memory: Val
assert proof.shard_parent_block.beacon_chain_root == hash_tree_root(proof.header)
committee = compute_committee(proof.header, validator_memory)
# Verify that we have >=50% support
support_balance = sum([v.effective_balance for i, v in enumerate(committee) if get_bitfield_bit(proof.shard_bitfield, i) is True])
support_balance = sum([v.effective_balance for i, v in enumerate(committee) if proof.shard_bits[i]])
total_balance = sum([v.effective_balance for i, v in enumerate(committee)])
assert support_balance * 2 > total_balance
# Verify shard attestations
group_public_key = bls_aggregate_pubkeys([
v.pubkey for v, index in enumerate(committee)
if get_bitfield_bit(proof.shard_bitfield, index) is True
if proof.shard_bits[index]
])
assert bls_verify(
pubkey=group_public_key,
message_hash=hash_tree_root(shard_parent_block),
signature=proof.shard_aggregate_signature,
domain=get_domain(state, slot_to_epoch(shard_block.slot), DOMAIN_SHARD_ATTESTER),
domain=get_domain(state, compute_epoch_of_slot(shard_block.slot), DOMAIN_SHARD_ATTESTER),
)
```
The size of this proof is only 200 (header) + 96 (signature) + 16 (bitfield) + 352 (shard block) = 664 bytes. It can be reduced further by replacing `ShardBlock` with `MerklePartial(lambda x: x.beacon_chain_root, ShardBlock)`, which would cut off ~220 bytes.
The size of this proof is only 200 (header) + 96 (signature) + 16 (bits) + 352 (shard block) = 664 bytes. It can be reduced further by replacing `ShardBlock` with `MerklePartial(lambda x: x.beacon_chain_root, ShardBlock)`, which would cut off ~220 bytes.

View File

@ -95,8 +95,8 @@ Since some clients are waiting for `libp2p` implementations in their respective
(
network_id: uint8
chain_id: uint64
latest_finalized_root: bytes32
latest_finalized_epoch: uint64
finalized_root: bytes32
finalized_epoch: uint64
best_root: bytes32
best_slot: uint64
)
@ -107,7 +107,7 @@ Clients exchange `hello` messages upon connection, forming a two-phase handshake
Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions:
1. If `network_id` belongs to a different chain, since the client definitionally cannot sync with this client.
2. If the `latest_finalized_root` shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 in the diagram below has `(root, epoch)` of `(A, 5)` and Peer 2 has `(B, 3)`, Peer 1 would disconnect because it knows that `B` is not the root in their chain at epoch 3:
2. If the `finalized_root` shared by the peer is not in the client's chain at the expected epoch. For example, if Peer 1 in the diagram below has `(root, epoch)` of `(A, 5)` and Peer 2 has `(B, 3)`, Peer 1 would disconnect because it knows that `B` is not the root in their chain at epoch 3:
```
Root A
@ -136,7 +136,7 @@ Root B ^
+---+
```
Once the handshake completes, the client with the higher `latest_finalized_epoch` or `best_slot` (if the clients have equal `latest_finalized_epoch`s) SHOULD request beacon block roots from its counterparty via `beacon_block_roots` (i.e. RPC method `10`).
Once the handshake completes, the client with the higher `finalized_epoch` or `best_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon block roots from its counterparty via `beacon_block_roots` (i.e. RPC method `10`).
### Goodbye

View File

@ -11,13 +11,17 @@
- [Typing](#typing)
- [Basic types](#basic-types)
- [Composite types](#composite-types)
- [Variable-size and fixed-size](#variable-size-and-fixed-size)
- [Aliases](#aliases)
- [Default values](#default-values)
- [`is_empty`](#is_empty)
- [Illegal types](#illegal-types)
- [Serialization](#serialization)
- [`"uintN"`](#uintn)
- [`"bool"`](#bool)
- [`"null`](#null)
- [`uintN`](#uintn)
- [`boolean`](#boolean)
- [`null`](#null)
- [`Bitvector[N]`](#bitvectorn)
- [`Bitlist[N]`](#bitlistn)
- [Vectors, containers, lists, unions](#vectors-containers-lists-unions)
- [Deserialization](#deserialization)
- [Merkleization](#merkleization)
@ -37,71 +41,101 @@
## Typing
### Basic types
* `"uintN"`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`)
* `"bool"`: `True` or `False`
* `uintN`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`)
* `boolean`: `True` or `False`
### Composite types
* **container**: ordered heterogeneous collection of values
* key-pair curly bracket notation `{}`, e.g. `{"foo": "uint64", "bar": "bool"}`
* **vector**: ordered fixed-length homogeneous collection of values
* angle bracket notation `[type, N]`, e.g. `["uint64", N]`
* **list**: ordered variable-length homogeneous collection of values
* angle bracket notation `[type]`, e.g. `["uint64"]`
* python dataclass notation with key-type pairs, e.g.
```python
class ContainerExample(Container):
foo: uint64
bar: boolean
```
* **vector**: ordered fixed-length homogeneous collection, with `N` values
* notation `Vector[type, N]`, e.g. `Vector[uint64, N]`
* **list**: ordered variable-length homogeneous collection, limited to `N` values
* notation `List[type, N]`, e.g. `List[uint64, N]`
* **bitvector**: ordered fixed-length collection of `boolean` values, with `N` bits
* notation `Bitvector[N]`
* **bitlist**: ordered variable-length collection of `boolean` values, limited to `N` bits
* notation `Bitlist[N]`
* **union**: union type containing one of the given subtypes
* round bracket notation `(type_1, type_2, ...)`, e.g. `("null", "uint64")`
* notation `Union[type_1, type_2, ...]`, e.g. `union[null, uint64]`
### Variable-size and fixed-size
We recursively define "variable-size" types to be lists and unions and all types that contain a variable-size type. All other types are said to be "fixed-size".
We recursively define "variable-size" types to be lists, unions, `Bitlist` and all types that contain a variable-size type. All other types are said to be "fixed-size".
### Aliases
For convenience we alias:
* `"byte"` to `"uint8"` (this is a basic type)
* `"bytes"` to `["byte"]` (this is *not* a basic type)
* `"bytesN"` to `["byte", N]` (this is *not* a basic type)
* `"null"`: `{}`, i.e. the empty container
* `bit` to `boolean`
* `byte` to `uint8` (this is a basic type)
* `BytesN` to `Vector[byte, N]` (this is *not* a basic type)
* `null`: `{}`, i.e. the empty container
### Default values
The default value of a type upon initialization is recursively defined using `0` for `"uintN"`, `False` for `"bool"`, and `[]` for lists. Unions default to the first type in the union (with type index zero), which is `"null"` if present in the union.
The default value of a type upon initialization is recursively defined using `0` for `uintN`, `False` for `boolean` and the elements of `Bitvector`, and `[]` for lists and `Bitlist`. Unions default to the first type in the union (with type index zero), which is `null` if present in the union.
#### `is_empty`
An SSZ object is called empty (and thus `is_empty(object)` returns true) if it is equal to the default value for that type.
An SSZ object is called empty (and thus, `is_empty(object)` returns true) if it is equal to the default value for that type.
### Illegal types
Empty vector types (i.e. `[subtype, 0]` for some `subtype`) are not legal. The `"null"` type is only legal as the first type in a union subtype (i.e., with type index zero).
Empty vector types (i.e. `[subtype, 0]` for some `subtype`) are not legal. The `null` type is only legal as the first type in a union subtype (i.e. with type index zero).
## Serialization
We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `"bytes"`.
We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `bytes`.
> *Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signing_root`, `is_variable_size`, etc.) objects implicitly carry their type.
*Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signing_root`, `is_variable_size`, etc.) objects implicitly carry their type.
### `"uintN"`
### `uintN`
```python
assert N in [8, 16, 32, 64, 128, 256]
return value.to_bytes(N // 8, "little")
return value.to_bytes(N // BITS_PER_BYTE, "little")
```
### `"bool"`
### `boolean`
```python
assert value in (True, False)
return b"\x01" if value is True else b"\x00"
```
### `"null"`
### `null`
```python
return b""
```
### `Bitvector[N]`
```python
array = [0] * ((N + 7) // 8)
for i in range(N):
array[i // 8] |= value[i] << (i % 8)
return bytes(array)
```
### `Bitlist[N]`
Note that from the offset coding, the length (in bytes) of the bitlist is known. An additional leading `1` bit is added so that the length in bits will also be known.
```python
array = [0] * ((len(value) // 8) + 1)
for i in range(len(value)):
array[i // 8] |= value[i] << (i % 8)
array[len(value) // 8] |= 1 << (len(value) % 8)
return bytes(array)
```
### Vectors, containers, lists, unions
```python
@ -136,22 +170,46 @@ return serialized_type_index + serialized_bytes
Because serialization is an injective function (i.e. two distinct objects of the same type will serialize to different values) any bytestring has at most one object it could deserialize to. Efficient algorithms for computing this object can be found in [the implementations](#implementations).
Note that deserialization requires hardening against invalid inputs. A non-exhaustive list:
- Offsets: out of order, out of range, mismatching minimum element size.
- Scope: Extra unused bytes, not aligned with element size.
- More elements than a list limit allows. Part of enforcing consensus.
## Merkleization
We first define helper functions:
* `size_of(B)`, where `B` is a basic type: the length, in bytes, of the serialized form of the basic type.
* `chunk_count(type)`: calculate the amount of leafs for merkleization of the type.
* all basic types: `1`
* `Bitlist[N]` and `Bitvector[N]`: `(N + 255) // 256` (dividing by chunk size, rounding up)
* `List[B, N]` and `Vector[B, N]`, where `B` is a basic type: `(N * size_of(B) + 31) // 32` (dividing by chunk size, rounding up)
* `List[C, N]` and `Vector[C, N]`, where `C` is a composite type: `N`
* containers: `len(fields)`
* `bitfield_bytes(bits)`: return the bits of the bitlist or bitvector, packed in bytes, aligned to the start. Length-delimiting bit for bitlists is excluded.
* `pack`: Given ordered objects of the same basic type, serialize them, pack them into `BYTES_PER_CHUNK`-byte chunks, right-pad the last chunk with zero bytes, and return the chunks.
* `merkleize`: Given ordered `BYTES_PER_CHUNK`-byte chunks, if necessary append zero chunks so that the number of chunks is a power of two, Merkleize the chunks, and return the root. Note that `merkleize` on a single chunk is simply that chunk, i.e. the identity when the number of chunks is one.
* `next_pow_of_two(i)`: get the next power of 2 of `i`, if not already a power of 2, with 0 mapping to 1. Examples: `0->1, 1->1, 2->2, 3->4, 4->4, 6->8, 9->16`
* `merkleize(chunks, limit=None)`: Given ordered `BYTES_PER_CHUNK`-byte chunks, merkleize the chunks, and return the root:
* The merkleization depends on the effective input, which can be padded/limited:
- if no limit: pad the `chunks` with zeroed chunks to `next_pow_of_two(len(chunks))` (virtually for memory efficiency).
- if `limit > len(chunks)`, pad the `chunks` with zeroed chunks to `next_pow_of_two(limit)` (virtually for memory efficiency).
- if `limit < len(chunks)`: do not merkleize, input exceeds limit. Raise an error instead.
* Then, merkleize the chunks (empty input is padded to 1 zero chunk):
- If `1` chunk: the root is the chunk itself.
- If `> 1` chunks: merkleize as binary tree.
* `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` little-endian serialization) return `hash(root + length)`.
* `mix_in_type`: Given a Merkle root `root` and a type_index `type_index` (`"uint256"` little-endian serialization) return `hash(root + type_index)`.
We now define Merkleization `hash_tree_root(value)` of an object `value` recursively:
* `merkleize(pack(value))` if `value` is a basic object or a vector of basic objects
* `mix_in_length(merkleize(pack(value)), len(value))` if `value` is a list of basic objects
* `merkleize([hash_tree_root(element) for element in value])` if `value` is a vector of composite objects or a container
* `mix_in_length(merkleize([hash_tree_root(element) for element in value]), len(value))` if `value` is a list of composite objects
* `mix_in_type(merkleize(value.value), value.type_index)` if `value` is of union type
* `merkleize(pack(value))` if `value` is a basic object or a vector of basic objects.
* `merkleize(bitfield_bytes(value), limit=chunk_count(type))` if `value` is a bitvector.
* `mix_in_length(merkleize(pack(value), limit=chunk_count(type)), len(value))` if `value` is a list of basic objects.
* `mix_in_length(merkleize(bitfield_bytes(value), limit=chunk_count(type)), len(value))` if `value` is a bitlist.
* `merkleize([hash_tree_root(element) for element in value])` if `value` is a vector of composite objects or a container.
* `mix_in_length(merkleize([hash_tree_root(element) for element in value], limit=chunk_count(type)), len(value))` if `value` is a list of composite objects.
* `mix_in_type(merkleize(value.value), value.type_index)` if `value` is of union type.
## Self-signed containers
@ -164,7 +222,7 @@ Let `value` be a self-signed container object. The convention is that the signat
| Python | Ethereum 2.0 | Ethereum Foundation | [https://github.com/ethereum/py-ssz](https://github.com/ethereum/py-ssz) |
| Rust | Lighthouse | Sigma Prime | [https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz](https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz) |
| Nim | Nimbus | Status | [https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) |
| Rust | Shasper | ParityTech | [https://github.com/paritytech/shasper/tree/master/util/ssz](https://github.com/paritytech/shasper/tree/master/util/ssz) |
| Rust | Shasper | ParityTech | [https://github.com/paritytech/shasper/tree/master/utils/ssz](https://github.com/paritytech/shasper/tree/master/util/ssz) |
| TypeScript | Lodestar | ChainSafe Systems | [https://github.com/ChainSafe/ssz-js](https://github.com/ChainSafe/ssz-js) |
| Java | Cava | ConsenSys | [https://www.github.com/ConsenSys/cava/tree/master/ssz](https://www.github.com/ConsenSys/cava/tree/master/ssz) |
| Go | Prysm | Prysmatic Labs | [https://github.com/prysmaticlabs/go-ssz](https://github.com/prysmaticlabs/go-ssz) |

View File

@ -33,7 +33,10 @@ The particular formats of specific types of tests (test suites) are defined in s
Test formats:
- [`bls`](./bls/README.md)
- [`epoch_processing`](./epoch_processing/README.md)
- [`genesis`](./genesis/README.md)
- [`operations`](./operations/README.md)
- [`sanity`](./sanity/README.md)
- [`shuffling`](./shuffling/README.md)
- [`ssz_generic`](./ssz_generic/README.md)
- [`ssz_static`](./ssz_static/README.md)

View File

@ -6,8 +6,8 @@ A BLS compressed-hash to G2.
```yaml
input:
message: bytes32,
domain: bytes -- any number
message: bytes32
domain: bytes8 -- the BLS domain
output: List[bytes48] -- length of two
```

View File

@ -6,8 +6,8 @@ A BLS uncompressed-hash to G2.
```yaml
input:
message: bytes32,
domain: bytes -- any number
message: bytes32
domain: bytes8 -- the BLS domain
output: List[List[bytes48]] -- 3 lists, each a length of two
```

View File

@ -8,7 +8,7 @@ Message signing with BLS should produce a signature.
input:
privkey: bytes32 -- the private key used for signing
message: bytes32 -- input message to sign (a hash)
domain: bytes -- BLS domain
domain: bytes8 -- the BLS domain
output: bytes96 -- expected signature
```

View File

@ -17,13 +17,17 @@ post: BeaconState -- state after applying the epoch sub-transition.
## Condition
A handler of the `epoch_processing` test-runner should process these cases,
calling the corresponding processing implementation.
calling the corresponding processing implementation (same name, prefixed with `process_`).
This excludes the other parts of the epoch-transition.
The provided pre-state is already transitioned to just before the specific sub-transition of focus of the handler.
Sub-transitions:
| *`sub-transition-name`* | *`processing call`* |
|-------------------------|-----------------------------------|
| `crosslinks` | `process_crosslinks(state)` |
| `registry_updates` | `process_registry_updates(state)` |
- `justification_and_finalization`
- `crosslinks`
- *`rewards_and_penalties` - planned testing extension*
- `registry_updates`
- `slashings`
- `final_updates`
The resulting state should match the expected `post` state.

View File

@ -0,0 +1,8 @@
# Genesis tests
The aim of the genesis tests is to provide a baseline to test genesis-state initialization and test
if the proposed genesis-validity conditions are working.
There are two handlers, documented individually:
- [`validity`](./validity.md): Tests if a genesis state is valid, i.e. if it counts as trigger to launch.
- [`initialization`](./initialization.md): Tests the initialization of a genesis state based on Eth1 data.

View File

@ -0,0 +1,22 @@
# Genesis creation testing
Tests the initialization of a genesis state based on Eth1 data.
## Test case format
```yaml
description: string -- description of test case, purely for debugging purposes
bls_setting: int -- see general test-format spec.
eth1_block_hash: Bytes32 -- the root of the Eth-1 block, hex encoded, with prefix 0x
eth1_timestamp: int -- the timestamp of the block, in seconds.
deposits: [Deposit] -- list of deposits to build the genesis state with
state: BeaconState -- the expected genesis state.
```
To process this test, build a genesis state with the provided `eth1_block_hash`, `eth1_timestamp` and `deposits`:
`initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)`,
as described in the Beacon Chain specification.
## Condition
The resulting state should match the expected `state`.

View File

@ -0,0 +1,19 @@
# Genesis validity testing
Tests if a genesis state is valid, i.e. if it counts as trigger to launch.
## Test case format
```yaml
description: string -- description of test case, purely for debugging purposes
bls_setting: int -- see general test-format spec.
genesis: BeaconState -- state to validate.
is_valid: bool -- true if the genesis state is deemed valid as to launch with, false otherwise.
```
To process the data, call `is_valid_genesis_state(genesis)`.
## Condition
The result of calling `is_valid_genesis_state(genesis)` should match the expected `is_valid` boolean.

View File

@ -16,6 +16,7 @@ post: BeaconState -- state after applying the operation. No
A handler of the `operations` test-runner should process these cases,
calling the corresponding processing implementation.
This excludes the other parts of the block-transition.
Operations:
@ -23,7 +24,7 @@ Operations:
|-------------------------|----------------------|----------------------|--------------------------------------------------------|
| `attestation` | `Attestation` | `attestation` | `process_attestation(state, attestation)` |
| `attester_slashing` | `AttesterSlashing` | `attester_slashing` | `process_attester_slashing(state, attester_slashing)` |
| `block_header` | `Block` | `block` | `process_block_header(state, block)` |
| `block` | `Block` | `block` | `process_block_header(state, block)` |
| `deposit` | `Deposit` | `deposit` | `process_deposit(state, deposit)` |
| `proposer_slashing` | `ProposerSlashing` | `proposer_slashing` | `process_proposer_slashing(state, proposer_slashing)` |
| `transfer` | `Transfer` | `transfer` | `process_transfer(state, transfer)` |

View File

@ -8,15 +8,14 @@ Sanity tests to cover a series of one or more empty-slot transitions being proce
description: string -- description of test case, purely for debugging purposes
bls_setting: int -- see general test-format spec.
pre: BeaconState -- state before running through the transitions.
slots: N -- amount of slots to process, N being a positive numer.
slots: N -- amount of slots to process, N being a positive number.
post: BeaconState -- state after applying all the transitions.
```
The transition with pure time, no blocks, is known as `state_transition_to(state, slot)` in the spec.
The transition with pure time, no blocks, is known as `process_slots(state, slot)` in the spec.
This runs state-caching (pure slot transition) and epoch processing (every E slots).
To process the data, call `state_transition_to(pre, pre.slot + N)`. And see if `pre` mutated into the equivalent of `post`.
To process the data, call `process_slots(pre, pre.slot + N)`.
## Condition

View File

@ -44,8 +44,8 @@
- [Crosslink vote](#crosslink-vote)
- [Construct attestation](#construct-attestation)
- [Data](#data)
- [Aggregation bitfield](#aggregation-bitfield)
- [Custody bitfield](#custody-bitfield)
- [Aggregation bits](#aggregation-bits)
- [Custody bits](#custody-bits)
- [Aggregate signature](#aggregate-signature)
- [How to avoid slashing](#how-to-avoid-slashing)
- [Proposer slashing](#proposer-slashing)
@ -57,7 +57,7 @@
This document represents the expected behavior of an "honest validator" with respect to Phase 0 of the Ethereum 2.0 protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope.
A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof of work networks in which a miner provides collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol.
A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof-of-work networks in which miners provide collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol.
## Prerequisites
@ -86,75 +86,78 @@ Validator public keys are [G1 points](../bls_signature.md#g1-points) on the [BLS
A secondary withdrawal private key, `withdrawal_privkey`, must also be securely generated along with the resultant `withdrawal_pubkey`. This `withdrawal_privkey` does not have to be available for signing during the normal lifetime of a validator and can live in "cold storage".
The validator constructs their `withdrawal_credentials` via the following:
* Set `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE`.
* Set `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX`.
* Set `withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:]`.
### Submit deposit
In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 PoW chain. Deposits are made to the [deposit contract](../core/0_deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`.
In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 proof-of-work chain. Deposits are made to the [deposit contract](../core/0_deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`.
To submit a deposit:
* Pack the validator's [initialization parameters](#initialization) into `deposit_data`, a [`DepositData`](../core/0_beacon-chain.md#depositdata) SSZ object.
* Let `amount` be the amount in Gwei to be deposited by the validator where `MIN_DEPOSIT_AMOUNT <= amount <= MAX_EFFECTIVE_BALANCE`.
* Set `deposit_data.amount = amount`.
* Let `signature` be the result of `bls_sign` of the `signing_root(deposit_data)` with `domain=bls_domain(DOMAIN_DEPOSIT)`. (Deposits are valid regardless of fork version, `bls_domain` will default to zeroes there).
* Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])` along with a deposit of `amount` Gwei.
- Pack the validator's [initialization parameters](#initialization) into `deposit_data`, a [`DepositData`](../core/0_beacon-chain.md#depositdata) SSZ object.
- Let `amount` be the amount in Gwei to be deposited by the validator where `MIN_DEPOSIT_AMOUNT <= amount <= MAX_EFFECTIVE_BALANCE`.
- Set `deposit_data.amount = amount`.
- Let `signature` be the result of `bls_sign` of the `signing_root(deposit_data)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (Deposits are valid regardless of fork version, `compute_domain` will default to zeroes there).
- Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])` along with a deposit of `amount` Gwei.
*Note*: Deposits made for the same `pubkey` are treated as for the same validator. A singular `Validator` will be added to `state.validator_registry` with each additional deposit amount added to the validator's balance. A validator can only be activated when total deposits for the validator pubkey meet or exceed `MAX_EFFECTIVE_BALANCE`.
*Note*: Deposits made for the same `pubkey` are treated as for the same validator. A singular `Validator` will be added to `state.validators` with each additional deposit amount added to the validator's balance. A validator can only be activated when total deposits for the validator pubkey meet or exceed `MAX_EFFECTIVE_BALANCE`.
### Process deposit
Deposits cannot be processed into the beacon chain until the Eth 1.0 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth 1.0 blocks (~4 hours) plus `ETH1_DATA_VOTING_PERIOD` epochs (~1.7 hours). Once the requisite Eth 1.0 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validator_registry` within an epoch or two. The validator is then in a queue to be activated.
Deposits cannot be processed into the beacon chain until the Eth 1.0 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth 1.0 blocks (~4 hours) plus `ETH1_DATA_VOTING_PERIOD` epochs (~1.7 hours). Once the requisite Eth 1.0 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated.
### Validator index
Once a validator has been processed and added to the beacon state's `validator_registry`, the validator's `validator_index` is defined by the index into the registry at which the [`ValidatorRecord`](../core/0_beacon-chain.md#validator) contains the `pubkey` specified in the validator's deposit. A validator's `validator_index` is guaranteed to not change from the time of initial deposit until the validator exits and fully withdraws. This `validator_index` is used throughout the specification to dictate validator roles and responsibilities at any point and should be stored locally.
Once a validator has been processed and added to the beacon state's `validators`, the validator's `validator_index` is defined by the index into the registry at which the [`ValidatorRecord`](../core/0_beacon-chain.md#validator) contains the `pubkey` specified in the validator's deposit. A validator's `validator_index` is guaranteed to not change from the time of initial deposit until the validator exits and fully withdraws. This `validator_index` is used throughout the specification to dictate validator roles and responsibilities at any point and should be stored locally.
### Activation
In normal operation, the validator is quickly activated at which point the validator is added to the shuffling and begins validation after an additional `ACTIVATION_EXIT_DELAY` epochs (25.6 minutes).
In normal operation, the validator is quickly activated, at which point the validator is added to the shuffling and begins validation after an additional `ACTIVATION_EXIT_DELAY` epochs (25.6 minutes).
The function [`is_active_validator`](../core/0_beacon-chain.md#is_active_validator) can be used to check if a validator is active during a given epoch. Usage is as follows:
```python
validator = state.validator_registry[validator_index]
is_active = is_active_validator(validator, get_current_epoch(state))
def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool:
validator = state.validators[validator_index]
return is_active_validator(validator, get_current_epoch(state))
```
Once a validator is activated, the validator is assigned [responsibilities](#beacon-chain-responsibilities) until exited.
*Note*: There is a maximum validator churn per finalized epoch so the delay until activation is variable depending upon finality, total active validator balance, and the number of validators in the queue to be activated.
*Note*: There is a maximum validator churn per finalized epoch, so the delay until activation is variable depending upon finality, total active validator balance, and the number of validators in the queue to be activated.
## Validator assignments
A validator can get committee assignments for a given epoch using the following helper via `get_committee_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`.
```python
def get_committee_assignment(
state: BeaconState,
def get_committee_assignment(state: BeaconState,
epoch: Epoch,
validator_index: ValidatorIndex) -> Tuple[List[ValidatorIndex], Shard, Slot]:
validator_index: ValidatorIndex) -> Optional[Tuple[Sequence[ValidatorIndex], Shard, Slot]]:
"""
Return the committee assignment in the ``epoch`` for ``validator_index``.
``assignment`` returned is a tuple of the following form:
* ``assignment[0]`` is the list of validators in the committee
* ``assignment[1]`` is the shard to which the committee is assigned
* ``assignment[2]`` is the slot at which the committee is assigned
Return None if no assignment.
"""
next_epoch = get_current_epoch(state) + 1
assert epoch <= next_epoch
committees_per_slot = get_epoch_committee_count(state, epoch) // SLOTS_PER_EPOCH
epoch_start_slot = get_epoch_start_slot(epoch)
for slot in range(epoch_start_slot, epoch_start_slot + SLOTS_PER_EPOCH):
committees_per_slot = get_committee_count(state, epoch) // SLOTS_PER_EPOCH
start_slot = compute_start_slot_of_epoch(epoch)
for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH):
offset = committees_per_slot * (slot % SLOTS_PER_EPOCH)
slot_start_shard = (get_epoch_start_shard(state, epoch) + offset) % SHARD_COUNT
slot_start_shard = (get_start_shard(state, epoch) + offset) % SHARD_COUNT
for i in range(committees_per_slot):
shard = (slot_start_shard + i) % SHARD_COUNT
shard = Shard((slot_start_shard + i) % SHARD_COUNT)
committee = get_crosslink_committee(state, epoch, shard)
if validator_index in committee:
return committee, shard, slot
return committee, shard, Slot(slot)
return None
```
A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run with a `state` of the slot in question. Proposer selection is only stable within the context of the current epoch.
@ -183,7 +186,7 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo
A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function).
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312500 validators = 10 million ETH, that's once per ~3 weeks).
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~3 weeks).
#### Block header
@ -205,88 +208,84 @@ Set `block.state_root = hash_tree_root(state)` of the resulting `state` of the `
##### Randao reveal
Set `block.randao_reveal = epoch_signature` where `epoch_signature` is defined as:
Set `block.randao_reveal = epoch_signature` where `epoch_signature` is obtained from:
```python
epoch_signature = bls_sign(
privkey=validator.privkey, # privkey stored locally, not in state
message_hash=hash_tree_root(slot_to_epoch(block.slot)),
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot `block.slot`
epoch=slot_to_epoch(block.slot),
domain_type=DOMAIN_RANDAO,
)
)
def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_of_slot(block.slot))
return bls_sign(privkey, hash_tree_root(compute_epoch_of_slot(block.slot)), domain)
```
##### Eth1 Data
`block.eth1_data` is a mechanism used by block proposers vote on a recent Ethereum 1.0 block hash and an associated deposit root found in the Ethereum 1.0 deposit contract. When consensus is formed, `state.latest_eth1_data` is updated, and validator deposits up to this root can be processed. The deposit root can be calculated by calling the `get_deposit_root()` function of the deposit contract using the post-state of the block hash.
The `block.eth1_data` field is for block proposers to vote on recent Eth 1.0 data. This recent data contains an Eth 1.0 block hash as well as the associated deposit root (as calculated by the `get_hash_tree_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth 1.0 block. If over half of the block proposers in the current Eth 1.0 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
* Let `D` be the list of `Eth1DataVote` objects `vote` in `state.eth1_data_votes` where:
* `vote.eth1_data.block_hash` is the hash of an Eth 1.0 block that is (i) part of the canonical chain, (ii) >= `ETH1_FOLLOW_DISTANCE` blocks behind the head, and (iii) newer than `state.latest_eth1_data.block_hash`.
* `vote.eth1_data.deposit_count` is the deposit count of the Eth 1.0 deposit contract at the block defined by `vote.eth1_data.block_hash`.
* `vote.eth1_data.deposit_root` is the deposit root of the Eth 1.0 deposit contract at the block defined by `vote.eth1_data.block_hash`.
* If `D` is empty:
* Let `block_hash` be the block hash of the `ETH1_FOLLOW_DISTANCE`'th ancestor of the head of the canonical Eth 1.0 chain.
* Let `deposit_root` and `deposit_count` be the deposit root and deposit count of the Eth 1.0 deposit contract in the post-state of the block referenced by `block_hash`
* Let `best_vote_data = Eth1Data(block_hash=block_hash, deposit_root=deposit_root, deposit_count=deposit_count)`.
* If `D` is nonempty:
* Let `best_vote_data` be the `eth1_data` member of `D` that has the highest vote count (`D.count(eth1_data)`), breaking ties by favoring block hashes with higher associated block height.
* Set `block.eth1_data = best_vote_data`.
Let `get_eth1_data(distance: uint64) -> Eth1Data` be the (subjective) function that returns the Eth 1.0 data at distance `distance` relative to the Eth 1.0 head at the start of the current Eth 1.0 voting period. Let `previous_eth1_distance` be the distance relative to the Eth 1.0 block corresponding to `state.eth1_data.block_hash` at the start of the current Eth 1.0 voting period. An honest block proposer sets `block.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where:
```python
def get_eth1_vote(state: BeaconState, previous_eth1_distance: uint64) -> Eth1Data:
new_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, 2 * ETH1_FOLLOW_DISTANCE)]
all_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, previous_eth1_distance)]
valid_votes = []
for slot, vote in enumerate(state.eth1_data_votes):
period_tail = slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_squareroot(SLOTS_PER_ETH1_VOTING_PERIOD)
if vote in new_eth1_data or (period_tail and vote in all_eth1_data):
valid_votes.append(vote)
return max(
valid_votes,
key=lambda v: (valid_votes.count(v), -all_eth1_data.index(v)), # Tiebreak by smallest distance
default=get_eth1_data(ETH1_FOLLOW_DISTANCE),
)
```
##### Signature
Set `block.signature = block_signature` where `block_signature` is defined as:
Set `header.signature = block_signature` where `block_signature` is obtained from:
```python
block_signature = bls_sign(
privkey=validator.privkey, # privkey store locally, not in state
message_hash=signing_root(block),
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot `block.slot`
epoch=slot_to_epoch(block.slot),
domain_type=DOMAIN_BEACON_BLOCK,
)
)
def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_of_slot(header.slot))
return bls_sign(privkey, signing_root(header), domain)
```
#### Block body
##### Proposer slashings
Up to `MAX_PROPOSER_SLASHINGS` [`ProposerSlashing`](../core/0_beacon-chain.md#proposerslashing) objects can be included in the `block`. The proposer slashings must satisfy the verification conditions found in [proposer slashings processing](../core/0_beacon-chain.md#proposer-slashings). The validator receives a small "whistleblower" reward for each proposer slashing found and included.
Up to `MAX_PROPOSER_SLASHINGS`, [`ProposerSlashing`](../core/0_beacon-chain.md#proposerslashing) objects can be included in the `block`. The proposer slashings must satisfy the verification conditions found in [proposer slashings processing](../core/0_beacon-chain.md#proposer-slashings). The validator receives a small "whistleblower" reward for each proposer slashing found and included.
##### Attester slashings
Up to `MAX_ATTESTER_SLASHINGS` [`AttesterSlashing`](../core/0_beacon-chain.md#attesterslashing) objects can be included in the `block`. The attester slashings must satisfy the verification conditions found in [Attester slashings processing](../core/0_beacon-chain.md#attester-slashings). The validator receives a small "whistleblower" reward for each attester slashing found and included.
Up to `MAX_ATTESTER_SLASHINGS`, [`AttesterSlashing`](../core/0_beacon-chain.md#attesterslashing) objects can be included in the `block`. The attester slashings must satisfy the verification conditions found in [attester slashings processing](../core/0_beacon-chain.md#attester-slashings). The validator receives a small "whistleblower" reward for each attester slashing found and included.
##### Attestations
Up to `MAX_ATTESTATIONS` aggregate attestations can be included in the `block`. The attestations added must satisfy the verification conditions found in [attestation processing](../core/0_beacon-chain.md#attestations). To maximize profit, the validator should attempt to gather aggregate attestations that include singular attestations from the largest number of validators whose signatures from the same epoch have not previously been added on chain.
Up to `MAX_ATTESTATIONS`, aggregate attestations can be included in the `block`. The attestations added must satisfy the verification conditions found in [attestation processing](../core/0_beacon-chain.md#attestations). To maximize profit, the validator should attempt to gather aggregate attestations that include singular attestations from the largest number of validators whose signatures from the same epoch have not previously been added on chain.
##### Deposits
If there are any unprocessed deposits for the existing `state.latest_eth1_data` (i.e. `state.latest_eth1_data.deposit_count > state.deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, latest_eth1_data.deposit_count - state.deposit_index)`. These [`deposits`](../core/0_beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth 1.0 deposit contract](../core/0_deposit-contract) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](../core/0_beacon-chain.md#deposits).
If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. `state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. These [`deposits`](../core/0_beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth 1.0 deposit contract](../core/0_deposit-contract) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](../core/0_beacon-chain.md#deposits).
The `proof` for each deposit must be constructed against the deposit root contained in `state.latest_eth1_data` rather than the deposit root at the time the deposit was initially logged from the 1.0 chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `latest_eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation.
The `proof` for each deposit must be constructed against the deposit root contained in `state.eth1_data` rather than the deposit root at the time the deposit was initially logged from the 1.0 chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation.
##### Voluntary exits
Up to `MAX_VOLUNTARY_EXITS` [`VoluntaryExit`](../core/0_beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](../core/0_beacon-chain.md#voluntary-exits).
Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](../core/0_beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](../core/0_beacon-chain.md#voluntary-exits).
### Attestations
A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `shard`, and assigned `slot` for which the validator performs this role during an epoch is defined by `get_committee_assignment(state, epoch, validator_index)`.
A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `shard`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`.
A validator should create and broadcast the attestation halfway through the `slot` during which the validator is assigned that is, `SECONDS_PER_SLOT * 0.5` seconds after the start of `slot`.
A validator should create and broadcast the attestation halfway through the `slot` during which the validator is assigned―that is, `SECONDS_PER_SLOT * 0.5` seconds after the start of `slot`.
#### Attestation data
First the validator should construct `attestation_data`, an [`AttestationData`](../core/0_beacon-chain.md#attestationdata) object based upon the state at the assigned slot.
First, the validator should construct `attestation_data`, an [`AttestationData`](../core/0_beacon-chain.md#attestationdata) object based upon the state at the assigned slot.
* Let `head_block` be the result of running the fork choice during the assigned slot.
* Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`.
- Let `head_block` be the result of running the fork choice during the assigned slot.
- Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`.
##### LMD GHOST vote
@ -294,77 +293,65 @@ Set `attestation_data.beacon_block_root = signing_root(head_block)`.
##### FFG vote
* Set `attestation_data.source_epoch = head_state.current_justified_epoch`.
* Set `attestation_data.source_root = head_state.current_justified_root`.
* Set `attestation_data.target_epoch = get_current_epoch(head_state)`
* Set `attestation_data.target_root = epoch_boundary_block_root` where `epoch_boundary_block_root` is the root of block at the most recent epoch boundary.
- Set `attestation_data.source = head_state.current_justified_checkpoint`.
- Set `attestation_data.target = Checkpoint(epoch=get_current_epoch(head_state), root=epoch_boundary_block_root)` where `epoch_boundary_block_root` is the root of block at the most recent epoch boundary.
*Note*: `epoch_boundary_block_root` can be looked up in the state using:
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
* Let `epoch_boundary_block_root = signing_root(head_block) if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.
- Let `start_slot = compute_start_slot_of_epoch(get_current_epoch(head_state))`.
- Let `epoch_boundary_block_root = signing_root(head_block) if start_slot == head_state.slot else get_block_root(state, start_slot)`.
##### Crosslink vote
Construct `attestation_data.crosslink` via the following.
* Set `attestation_data.crosslink.shard = shard` where `shard` is the shard associated with the validator's committee.
* Let `parent_crosslink = head_state.current_crosslinks[shard]`.
* Set `attestation_data.crosslink.start_epoch = parent_crosslink.end_epoch`.
* Set `attestation_data.crosslink.end_epoch = min(attestation_data.target_epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)`.
* Set `attestation_data.crosslink.parent_root = hash_tree_root(head_state.current_crosslinks[shard])`.
* Set `attestation_data.crosslink.data_root = ZERO_HASH`. *Note*: This is a stub for Phase 0.
- Set `attestation_data.crosslink.shard = shard` where `shard` is the shard associated with the validator's committee.
- Let `parent_crosslink = head_state.current_crosslinks[shard]`.
- Set `attestation_data.crosslink.start_epoch = parent_crosslink.end_epoch`.
- Set `attestation_data.crosslink.end_epoch = min(attestation_data.target.epoch, parent_crosslink.end_epoch + MAX_EPOCHS_PER_CROSSLINK)`.
- Set `attestation_data.crosslink.parent_root = hash_tree_root(head_state.current_crosslinks[shard])`.
- Set `attestation_data.crosslink.data_root = ZERO_HASH`. *Note*: This is a stub for Phase 0.
#### Construct attestation
Next the validator creates `attestation`, an [`Attestation`](../core/0_beacon-chain.md#attestation) object.
Next, the validator creates `attestation`, an [`Attestation`](../core/0_beacon-chain.md#attestation) object.
##### Data
Set `attestation.data = attestation_data` where `attestation_data` is the `AttestationData` object defined in the previous section, [attestation data](#attestation-data).
##### Aggregation bitfield
##### Aggregation bits
* Let `aggregation_bitfield` be a byte array filled with zeros of length `(len(committee) + 7) // 8`.
* Let `index_into_committee` be the index into the validator's `committee` at which `validator_index` is located.
* Set `aggregation_bitfield[index_into_committee // 8] |= 2 ** (index_into_committee % 8)`.
* Set `attestation.aggregation_bitfield = aggregation_bitfield`.
- Let `attestation.aggregation_bits` be a `Bitlist[MAX_INDICES_PER_ATTESTATION]` where the bits at the index in the aggregated validator's `committee` is set to `0b1`.
*Note*: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)` should return a list of length equal to 1, containing `validator_index`.
*Note*: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bits)` should return a list of length equal to 1, containing `validator_index`.
##### Custody bitfield
##### Custody bits
* Let `custody_bitfield` be a byte array filled with zeros of length `(len(committee) + 7) // 8`.
* Set `attestation.custody_bitfield = custody_bitfield`.
- Let `attestation.custody_bits` be a `Bitlist[MAX_INDICES_PER_ATTESTATION]` filled with zeros of length `len(committee)`.
*Note*: This is a stub for Phase 0.
##### Aggregate signature
Set `attestation.aggregate_signature = signed_attestation_data` where `signed_attestation_data` is defined as:
Set `attestation.signature = signed_attestation_data` where `signed_attestation_data` is obtained from:
```python
attestation_data_and_custody_bit = AttestationDataAndCustodyBit(
def get_signed_attestation_data(state: BeaconState, attestation: IndexedAttestation, privkey: int) -> BLSSignature:
attestation_data_and_custody_bit = AttestationDataAndCustodyBit(
data=attestation.data,
custody_bit=0b0,
)
attestation_message = hash_tree_root(attestation_data_and_custody_bit)
signed_attestation_data = bls_sign(
privkey=validator.privkey, # privkey stored locally, not in state
message_hash=attestation_message,
domain=get_domain(
fork=fork, # `fork` is the fork object at the slot, `attestation_data.slot`
epoch=slot_to_epoch(attestation_data.slot),
domain_type=DOMAIN_ATTESTATION,
)
)
domain = get_domain(state, DOMAIN_ATTESTATION, attestation.data.target.epoch)
return bls_sign(privkey, hash_tree_root(attestation_data_and_custody_bit), domain)
```
## How to avoid slashing
"Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed -- [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed.
"Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed: [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed.
*Note*: Signed data must be within a sequential `Fork` context to conflict. Messages cannot be slashed across diverging forks. If the previous fork version is 1 and the chain splits into fork 2 and 102, messages from 1 can slashable against messages in forks 1, 2, and 102. Messages in 2 cannot be slashable against messages in 102 and vice versa.
*Note*: Signed data must be within a sequential `Fork` context to conflict. Messages cannot be slashed across diverging forks. If the previous fork version is 1 and the chain splits into fork 2 and 102, messages from 1 can slashable against messages in forks 1, 2, and 102. Messages in 2 cannot be slashable against messages in 102, and vice versa.
### Proposer slashing
@ -373,17 +360,19 @@ To avoid "proposer slashings", a validator must not sign two conflicting [`Beaco
*In Phase 0, as long as the validator does not sign two different beacon blocks for the same epoch, the validator is safe against proposer slashings.*
Specifically, when signing a `BeaconBlock`, a validator should perform the following steps in the following order:
1. Save a record to hard disk that a beacon block has been signed for the `epoch=slot_to_epoch(block.slot)`.
1. Save a record to hard disk that a beacon block has been signed for the `epoch=compute_epoch_of_slot(block.slot)`.
2. Generate and broadcast the block.
If the software crashes at some point within this routine, then when the validator comes back online the hard disk has the record of the *potentially* signed/broadcast block and can effectively avoid slashing.
If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast block and can effectively avoid slashing.
### Attester slashing
To avoid "attester slashings", a validator must not sign two conflicting [`AttestationData`](../core/0_beacon-chain.md#attestationdata) objects, i.e. two attestations that satisfy [`is_slashable_attestation_data`](../core/0_beacon-chain.md#is_slashable_attestation_data).
Specifically, when signing an `Attestation`, a validator should perform the following steps in the following order:
1. Save a record to hard disk that an attestation has been signed for source -- `attestation_data.source_epoch` -- and target -- `slot_to_epoch(attestation_data.slot)`.
1. Save a record to hard disk that an attestation has been signed for source (i.e. `attestation_data.source.epoch`) and target (i.e. `attestation_data.target.epoch`).
2. Generate and broadcast attestation.
If the software crashes at some point within this routine, then when the validator comes back online the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing.
If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing.

View File

@ -1,28 +1,27 @@
# Ethereum 2.0 Phase 0 -- Beacon Node API for Validator
__NOTICE__: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- Honest Validator](0_beacon-chain-validator.md) that describes an API exposed by the beacon node, which enables the validator client to participate in the Ethereum 2.0 protocol.
**Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- Honest Validator](0_beacon-chain-validator.md) that describes an API exposed by the beacon node, which enables the validator client to participate in the Ethereum 2.0 protocol.
## Outline
This document outlines a minimal application programming interface (API) which is exposed by a beacon node for use by a validator client implementation which aims to facilitate [_phase 0_](../../README.md#phase-0) of Ethereum 2.0.
This document outlines a minimal application programming interface (API) which is exposed by a beacon node for use by a validator client implementation which aims to facilitate [Phase 0](../../README.md#phase-0) of Ethereum 2.0.
The API is a REST interface, accessed via HTTP, designed for use as a local communications protocol between binaries. The only supported return data type is currently JSON.
The API is a REST interface, accessed via HTTP, designed for use as a local communications protocol between binaries. Currently, the only supported return data type is JSON.
### Background
The beacon node maintains the state of the beacon chain by communicating with other beacon nodes in the Ethereum Serenity network. Conceptually, it does not maintain keypairs that participate with the beacon chain.
## Background
The validator client is a conceptually separate entity which utilizes private keys to perform validator related tasks on the beacon chain, which we call validator "duties". These duties include the production of beacon blocks and signing of attestations.
The beacon node maintains the state of the beacon chain by communicating with other beacon nodes in the Ethereum 2.0 network. Conceptually, it does not maintain keypairs that participate with the beacon chain.
The validator client is a conceptually separate entity which utilizes private keys to perform validator related tasks, called "duties", on the beacon chain. These duties include the production of beacon blocks and signing of attestations.
Since it is recommended to separate these concerns in the client implementations, we must clearly define the communication between them.
The goal of this specification is to promote interoperability between beacon nodes and validator clients derived from different projects and to encourage innovation in validator client implementations, independently from beacon node development. For example, the validator client from Lighthouse could communicate with a running instance of the beacon node from Prysm, or a staking pool might create a decentrally managed validator client which utilises the same API.
This specification is derived from a proposal and discussion on Issues [#1011](https://github.com/ethereum/eth2.0-specs/issues/1011) and [#1012](https://github.com/ethereum/eth2.0-specs/issues/1012)
The goal of this specification is to promote interoperability between beacon nodes and validator clients derived from different projects and to encourage innovation in validator client implementations, independently from beacon node development. For example, the validator client from [Lighthouse](https://github.com/sigp/lighthouse) could communicate with a running instance of the beacon node from [Prysm](https://github.com/prysmaticlabs/prysm), or a staking pool might create a decentrally managed validator client which utilizes the same API.
This specification is derived from a proposal and discussion on Issues [#1011](https://github.com/ethereum/eth2.0-specs/issues/1011) and [#1012](https://github.com/ethereum/eth2.0-specs/issues/1012).
## Specification
The API specification has been written in [OpenAPI 3.0](https://swagger.io/docs/specification/about/) and is provided in the [beacon_node_oapi.yaml](beacon_node_oapi.yaml) file alongside this document.
For convenience, this specification has been uploaded to [SwaggerHub](https://swagger.io/tools/swaggerhub/) at the following URL:
[https://app.swaggerhub.com/apis/spble/beacon_node_api_for_validator](https://app.swaggerhub.com/apis/spble/beacon_node_api_for_validator)
For convenience, this specification has been uploaded to SwaggerHub [here](https://app.swaggerhub.com/apis/spble/beacon_node_api_for_validator).

View File

@ -415,16 +415,16 @@ components:
type: object
description: "The [`Attestation`](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestation) object from the Eth2.0 spec."
properties:
aggregation_bitfield:
aggregation_bits:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]+$"
description: "Attester aggregation bitfield."
custody_bitfield:
description: "Attester aggregation bits."
custody_bits:
type: string
format: byte
pattern: "^0x[a-fA-F0-9]+$"
description: "Custody bitfield."
description: "Custody bits."
signature:
type: string
format: byte

View File

@ -38,7 +38,7 @@ The `-j N` flag makes the generators run in parallel, with `N` being the amount
The makefile auto-detects generators in the `test_generators` directory and provides a tests-gen target for each generator. See example:
```bash
make ./yaml_tests/shuffling/
make ./eth2.0-spec-tests/tests/shuffling/
```
## Developing a generator

View File

@ -9,7 +9,7 @@ The base unit is bytes48 of which only 381 bits are used
## Resources
- [Eth2.0 spec](https://github.com/ethereum/eth2.0-specs/blob/master/specs/bls_signature.md)
- [Eth2.0 spec](../../specs/bls_signature.md)
- [Finite Field Arithmetic](http://www.springeronline.com/sgw/cda/pageitems/document/cda_downloaddocument/0,11996,0-0-45-110359-0,00.pdf)
- Chapter 2 of [Elliptic Curve Cryptography](http://cacr.uwaterloo.ca/ecc/). Darrel Hankerson, Alfred Menezes, and Scott Vanstone
- [Zcash BLS parameters](https://github.com/zkcrypto/pairing/tree/master/src/bls12_381)

View File

@ -5,30 +5,34 @@ BLS test vectors generator
from typing import Tuple
from eth_utils import (
to_tuple, int_to_big_endian
encode_hex,
int_to_big_endian,
to_tuple,
)
from gen_base import gen_runner, gen_suite, gen_typing
from py_ecc import bls
def int_to_hex(n: int) -> str:
return '0x' + int_to_big_endian(n).hex()
F2Q_COEFF_LEN = 48
G2_COMPRESSED_Z_LEN = 48
def int_to_hex(n: int, byte_length: int=None) -> str:
byte_value = int_to_big_endian(n)
if byte_length:
byte_value = byte_value.rjust(byte_length, b'\x00')
return encode_hex(byte_value)
def hex_to_int(x: str) -> int:
return int(x, 16)
# Note: even though a domain is only an uint64,
# To avoid issues with YAML parsers that are limited to 53-bit (JS language limit)
# It is serialized as an hex string as well.
DOMAINS = [
0,
1,
1234,
2**32-1,
2**64-1
b'\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x00\x00\x00\x00\x00\x00\x00\x01',
b'\xff\xff\xff\xff\xff\xff\xff\xff'
]
MESSAGES = [
@ -47,36 +51,35 @@ PRIVKEYS = [
def hash_message(msg: bytes,
domain: int) ->Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]]:
domain: bytes) ->Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]]:
"""
Hash message
Input:
- Message as bytes
- domain as uint64
- Message as bytes32
- domain as bytes8
Output:
- Message hash as a G2 point
"""
return [
[
int_to_hex(fq2.coeffs[0]),
int_to_hex(fq2.coeffs[1]),
int_to_hex(fq2.coeffs[0], F2Q_COEFF_LEN),
int_to_hex(fq2.coeffs[1], F2Q_COEFF_LEN),
]
for fq2 in bls.utils.hash_to_G2(msg, domain)
]
def hash_message_compressed(msg: bytes, domain: int) -> Tuple[str, str]:
def hash_message_compressed(msg: bytes, domain: bytes) -> Tuple[str, str]:
"""
Hash message
Input:
- Message as bytes
- domain as uint64
- Message as bytes32
- domain as bytes8
Output:
- Message hash as a compressed G2 point
"""
z1, z2 = bls.utils.compress_G2(bls.utils.hash_to_G2(msg, domain))
return [int_to_hex(z1), int_to_hex(z2)]
return [int_to_hex(z1, G2_COMPRESSED_Z_LEN), int_to_hex(z2, G2_COMPRESSED_Z_LEN)]
@to_tuple
@ -85,8 +88,8 @@ def case01_message_hash_G2_uncompressed():
for domain in DOMAINS:
yield {
'input': {
'message': '0x' + msg.hex(),
'domain': int_to_hex(domain)
'message': encode_hex(msg),
'domain': encode_hex(domain),
},
'output': hash_message(msg, domain)
}
@ -97,8 +100,8 @@ def case02_message_hash_G2_compressed():
for domain in DOMAINS:
yield {
'input': {
'message': '0x' + msg.hex(),
'domain': int_to_hex(domain)
'message': encode_hex(msg),
'domain': encode_hex(domain),
},
'output': hash_message_compressed(msg, domain)
}
@ -122,10 +125,10 @@ def case04_sign_messages():
yield {
'input': {
'privkey': int_to_hex(privkey),
'message': '0x' + message.hex(),
'domain': int_to_hex(domain)
'message': encode_hex(message),
'domain': encode_hex(domain),
},
'output': '0x' + sig.hex()
'output': encode_hex(sig)
}
# TODO: case05_verify_messages: Verify messages signed in case04
@ -138,17 +141,17 @@ def case06_aggregate_sigs():
for message in MESSAGES:
sigs = [bls.sign(message, privkey, domain) for privkey in PRIVKEYS]
yield {
'input': ['0x' + sig.hex() for sig in sigs],
'output': '0x' + bls.aggregate_signatures(sigs).hex(),
'input': [encode_hex(sig) for sig in sigs],
'output': encode_hex(bls.aggregate_signatures(sigs)),
}
@to_tuple
def case07_aggregate_pubkeys():
pubkeys = [bls.privtopub(privkey) for privkey in PRIVKEYS]
pubkeys_serial = ['0x' + pubkey.hex() for pubkey in pubkeys]
pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys]
yield {
'input': pubkeys_serial,
'output': '0x' + bls.aggregate_pubkeys(pubkeys).hex(),
'output': encode_hex(bls.aggregate_pubkeys(pubkeys)),
}

View File

@ -1,3 +1,3 @@
py-ecc==1.7.0
py_ecc==1.7.1
eth-utils==1.6.0
../../test_libs/gen_helpers

View File

@ -4,7 +4,10 @@ from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.phase_0.epoch_processing import (
test_process_crosslinks,
test_process_registry_updates
test_process_final_updates,
test_process_justification_and_finalization,
test_process_registry_updates,
test_process_slashings
)
from gen_base import gen_runner, gen_suite, gen_typing
from gen_from_tests.gen import generate_from_tests
@ -35,8 +38,16 @@ if __name__ == "__main__":
gen_runner.run_generator("epoch_processing", [
create_suite('crosslinks', 'minimal', lambda: generate_from_tests(test_process_crosslinks, 'phase0')),
create_suite('crosslinks', 'mainnet', lambda: generate_from_tests(test_process_crosslinks, 'phase0')),
create_suite('final_updates', 'minimal', lambda: generate_from_tests(test_process_final_updates, 'phase0')),
create_suite('final_updates', 'mainnet', lambda: generate_from_tests(test_process_final_updates, 'phase0')),
create_suite('justification_and_finalization', 'minimal',
lambda: generate_from_tests(test_process_justification_and_finalization, 'phase0')),
create_suite('justification_and_finalization', 'mainnet',
lambda: generate_from_tests(test_process_justification_and_finalization, 'phase0')),
create_suite('registry_updates', 'minimal',
lambda: generate_from_tests(test_process_registry_updates, 'phase0')),
create_suite('registry_updates', 'mainnet',
lambda: generate_from_tests(test_process_registry_updates, 'phase0')),
create_suite('slashings', 'minimal', lambda: generate_from_tests(test_process_slashings, 'phase0')),
create_suite('slashings', 'mainnet', lambda: generate_from_tests(test_process_slashings, 'phase0')),
])

View File

@ -0,0 +1,8 @@
# Genesis test generator
Genesis tests cover the initialization and validity-based launch trigger for the Beacon Chain genesis state.
Information on the format of the tests can be found in the [genesis test formats documentation](../../specs/test_formats/genesis/README.md).

View File

@ -0,0 +1,33 @@
from typing import Callable, Iterable
from eth2spec.test.genesis import test_initialization, test_validity
from gen_base import gen_runner, gen_suite, gen_typing
from gen_from_tests.gen import generate_from_tests
from preset_loader import loader
from eth2spec.phase0 import spec as spec
def create_suite(handler_name: str, config_name: str, get_cases: Callable[[], Iterable[gen_typing.TestCase]]) \
-> Callable[[str], gen_typing.TestSuiteOutput]:
def suite_definition(configs_path: str) -> gen_typing.TestSuiteOutput:
presets = loader.load_presets(configs_path, config_name)
spec.apply_constants_preset(presets)
return ("genesis_%s_%s" % (handler_name, config_name), handler_name, gen_suite.render_suite(
title="genesis testing",
summary="Genesis test suite, %s type, generated from pytests" % handler_name,
forks_timeline="testing",
forks=["phase0"],
config=config_name,
runner="genesis",
handler=handler_name,
test_cases=get_cases()))
return suite_definition
if __name__ == "__main__":
gen_runner.run_generator("genesis", [
create_suite('initialization', 'minimal', lambda: generate_from_tests(test_initialization, 'phase0')),
create_suite('validity', 'minimal', lambda: generate_from_tests(test_validity, 'phase0')),
])

View File

@ -0,0 +1,4 @@
eth-utils==1.6.0
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -49,7 +49,9 @@ if __name__ == "__main__":
create_suite('proposer_slashing', 'minimal', lambda: generate_from_tests(test_process_proposer_slashing, 'phase0')),
create_suite('proposer_slashing', 'mainnet', lambda: generate_from_tests(test_process_proposer_slashing, 'phase0')),
create_suite('transfer', 'minimal', lambda: generate_from_tests(test_process_transfer, 'phase0')),
create_suite('transfer', 'mainnet', lambda: generate_from_tests(test_process_transfer, 'phase0')),
# Disabled, due to the high amount of different transfer tests, this produces a shocking size of tests.
# Unnecessarily, as transfer are disabled currently, so not a priority.
# create_suite('transfer', 'mainnet', lambda: generate_from_tests(test_process_transfer, 'phase0')),
create_suite('voluntary_exit', 'minimal', lambda: generate_from_tests(test_process_voluntary_exit, 'phase0')),
create_suite('voluntary_exit', 'mainnet', lambda: generate_from_tests(test_process_voluntary_exit, 'phase0')),
])

View File

@ -10,7 +10,7 @@ from preset_loader import loader
def shuffling_case(seed, count):
yield 'seed', '0x' + seed.hex()
yield 'count', count
yield 'shuffled', [spec.get_shuffled_index(i, count, seed) for i in range(count)]
yield 'shuffled', [int(spec.compute_shuffled_index(i, count, seed)) for i in range(count)]
@to_tuple

View File

@ -21,8 +21,8 @@ MAX_LIST_LENGTH = 10
@to_dict
def create_test_case_contents(value, typ):
yield "value", encode.encode(value, typ)
def create_test_case_contents(value):
yield "value", encode.encode(value)
yield "serialized", '0x' + serialize(value).hex()
yield "root", '0x' + hash_tree_root(value).hex()
if hasattr(value, "signature"):
@ -32,7 +32,7 @@ def create_test_case_contents(value, typ):
@to_dict
def create_test_case(rng: Random, name: str, typ, mode: random_value.RandomizationMode, chaos: bool):
value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
yield name, create_test_case_contents(value, typ)
yield name, create_test_case_contents(value)
def get_spec_ssz_types():

View File

@ -28,7 +28,7 @@ These tests are sanity tests, to verify if the spec itself is consistent.
#### Automated
Run `make test` from the root of the specs repository.
Run `make test` from the root of the specs repository (after running `make install_test` if have not before).
#### Manual
@ -50,6 +50,10 @@ pytest --config=minimal eth2spec
```
Note the package-name, this is to locate the tests.
### How to view code coverage report
Run `make open_cov` from the root of the specs repository after running `make test` to open the html code coverage report.
## Contributing

View File

@ -1,39 +1,29 @@
from typing import Any
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_list_type,
is_vector_type, is_bytes_type, is_bytesn_type, is_container_type,
read_vector_elem_type, read_list_elem_type,
SSZType, SSZValue, uint, Container, Bytes, List, boolean,
Vector, BytesN
)
def decode(data, typ):
if is_uint_type(typ):
return data
elif is_bool_type(typ):
assert data in (True, False)
return data
elif is_list_type(typ):
elem_typ = read_list_elem_type(typ)
return [decode(element, elem_typ) for element in data]
elif is_vector_type(typ):
elem_typ = read_vector_elem_type(typ)
return Vector(decode(element, elem_typ) for element in data)
elif is_bytes_type(typ):
return bytes.fromhex(data[2:])
elif is_bytesn_type(typ):
return BytesN(bytes.fromhex(data[2:]))
elif is_container_type(typ):
def decode(data: Any, typ: SSZType) -> SSZValue:
if issubclass(typ, (uint, boolean)):
return typ(data)
elif issubclass(typ, (List, Vector)):
return typ(decode(element, typ.elem_type) for element in data)
elif issubclass(typ, (Bytes, BytesN)):
return typ(bytes.fromhex(data[2:]))
elif issubclass(typ, Container):
temp = {}
for field, subtype in typ.get_fields():
temp[field] = decode(data[field], subtype)
if field + "_hash_tree_root" in data:
assert(data[field + "_hash_tree_root"][2:] ==
hash_tree_root(temp[field], subtype).hex())
for field_name, field_type in typ.get_fields().items():
temp[field_name] = decode(data[field_name], field_type)
if field_name + "_hash_tree_root" in data:
assert (data[field_name + "_hash_tree_root"][2:] ==
hash_tree_root(temp[field_name]).hex())
ret = typ(**temp)
if "hash_tree_root" in data:
assert(data["hash_tree_root"][2:] ==
hash_tree_root(ret, typ).hex())
assert (data["hash_tree_root"][2:] ==
hash_tree_root(ret).hex())
return ret
else:
raise Exception(f"Type not recognized: data={data}, typ={typ}")

View File

@ -1,36 +1,32 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_impl import hash_tree_root, serialize
from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_list_type, is_vector_type, is_container_type,
read_elem_type,
uint
uint, boolean,
Bitlist, Bitvector, Container
)
def encode(value, typ, include_hash_tree_roots=False):
if is_uint_type(typ):
if hasattr(typ, '__supertype__'):
typ = typ.__supertype__
def encode(value, include_hash_tree_roots=False):
if isinstance(value, uint):
# Larger uints are boxed and the class declares their byte length
if issubclass(typ, uint) and typ.byte_len > 8:
return str(value)
return value
elif is_bool_type(typ):
assert value in (True, False)
return value
elif is_list_type(typ) or is_vector_type(typ):
elem_typ = read_elem_type(typ)
return [encode(element, elem_typ, include_hash_tree_roots) for element in value]
elif isinstance(typ, type) and issubclass(typ, bytes): # both bytes and BytesN
if value.type().byte_len > 8:
return str(int(value))
return int(value)
elif isinstance(value, boolean):
return value == 1
elif isinstance(value, (Bitlist, Bitvector)):
return '0x' + serialize(value).hex()
elif isinstance(value, list): # normal python lists, ssz-List, Vector
return [encode(element, include_hash_tree_roots) for element in value]
elif isinstance(value, bytes): # both bytes and BytesN
return '0x' + value.hex()
elif is_container_type(typ):
elif isinstance(value, Container):
ret = {}
for field, subtype in typ.get_fields():
field_value = getattr(value, field)
ret[field] = encode(field_value, subtype, include_hash_tree_roots)
for field_value, field_name in zip(value, value.get_fields().keys()):
ret[field_name] = encode(field_value, include_hash_tree_roots)
if include_hash_tree_roots:
ret[field + "_hash_tree_root"] = '0x' + hash_tree_root(field_value, subtype).hex()
ret[field_name + "_hash_tree_root"] = '0x' + hash_tree_root(field_value).hex()
if include_hash_tree_roots:
ret["hash_tree_root"] = '0x' + hash_tree_root(value, typ).hex()
ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex()
return ret
else:
raise Exception(f"Type not recognized: value={value}, typ={typ}")
raise Exception(f"Type not recognized: value={value}, typ={value.type()}")

View File

@ -1,18 +1,13 @@
from random import Random
from typing import Any
from enum import Enum
from eth2spec.utils.ssz.ssz_impl import is_basic_type
from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_list_type,
is_vector_type, is_bytes_type, is_bytesn_type, is_container_type,
read_vector_elem_type, read_list_elem_type,
uint_byte_size
SSZType, SSZValue, BasicValue, BasicType, uint, Container, Bytes, List, boolean,
Vector, BytesN, Bitlist, Bitvector
)
# in bytes
UINT_SIZES = (1, 2, 4, 8, 16, 32)
UINT_BYTE_SIZES = (1, 2, 4, 8, 16, 32)
random_mode_names = ("random", "zero", "max", "nil", "one", "lengthy")
@ -39,11 +34,11 @@ class RandomizationMode(Enum):
def get_random_ssz_object(rng: Random,
typ: Any,
typ: SSZType,
max_bytes_length: int,
max_list_length: int,
mode: RandomizationMode,
chaos: bool) -> Any:
chaos: bool) -> SSZValue:
"""
Create an object for a given type, filled with random data.
:param rng: The random number generator to use.
@ -56,33 +51,31 @@ def get_random_ssz_object(rng: Random,
"""
if chaos:
mode = rng.choice(list(RandomizationMode))
if is_bytes_type(typ):
if issubclass(typ, Bytes):
# Bytes array
if mode == RandomizationMode.mode_nil_count:
return b''
return typ(b'')
elif mode == RandomizationMode.mode_max_count:
return get_random_bytes_list(rng, max_bytes_length)
return typ(get_random_bytes_list(rng, max_bytes_length))
elif mode == RandomizationMode.mode_one_count:
return get_random_bytes_list(rng, 1)
return typ(get_random_bytes_list(rng, 1))
elif mode == RandomizationMode.mode_zero:
return b'\x00'
return typ(b'\x00')
elif mode == RandomizationMode.mode_max:
return b'\xff'
return typ(b'\xff')
else:
return get_random_bytes_list(rng, rng.randint(0, max_bytes_length))
elif is_bytesn_type(typ):
# BytesN
length = typ.length
return typ(get_random_bytes_list(rng, rng.randint(0, max_bytes_length)))
elif issubclass(typ, BytesN):
# Sanity, don't generate absurdly big random values
# If a client is aiming to performance-test, they should create a benchmark suite.
assert length <= max_bytes_length
assert typ.length <= max_bytes_length
if mode == RandomizationMode.mode_zero:
return b'\x00' * length
return typ(b'\x00' * typ.length)
elif mode == RandomizationMode.mode_max:
return b'\xff' * length
return typ(b'\xff' * typ.length)
else:
return get_random_bytes_list(rng, length)
elif is_basic_type(typ):
return typ(get_random_bytes_list(rng, typ.length))
elif issubclass(typ, BasicValue):
# Basic types
if mode == RandomizationMode.mode_zero:
return get_min_basic_value(typ)
@ -90,32 +83,31 @@ def get_random_ssz_object(rng: Random,
return get_max_basic_value(typ)
else:
return get_random_basic_value(rng, typ)
elif is_vector_type(typ):
# Vector
elem_typ = read_vector_elem_type(typ)
return [
get_random_ssz_object(rng, elem_typ, max_bytes_length, max_list_length, mode, chaos)
elif issubclass(typ, Vector) or issubclass(typ, Bitvector):
return typ(
get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos)
for _ in range(typ.length)
]
elif is_list_type(typ):
# List
elem_typ = read_list_elem_type(typ)
length = rng.randint(0, max_list_length)
)
elif issubclass(typ, List) or issubclass(typ, Bitlist):
length = rng.randint(0, min(typ.length, max_list_length))
if mode == RandomizationMode.mode_one_count:
length = 1
elif mode == RandomizationMode.mode_max_count:
length = max_list_length
return [
get_random_ssz_object(rng, elem_typ, max_bytes_length, max_list_length, mode, chaos)
if typ.length < length: # SSZ imposes a hard limit on lists, we can't put in more than that
length = typ.length
return typ(
get_random_ssz_object(rng, typ.elem_type, max_bytes_length, max_list_length, mode, chaos)
for _ in range(length)
]
elif is_container_type(typ):
)
elif issubclass(typ, Container):
# Container
return typ(**{
field:
get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos)
for field, subtype in typ.get_fields()
field_name:
get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos)
for field_name, field_type in typ.get_fields().items()
})
else:
raise Exception(f"Type not recognized: typ={typ}")
@ -125,34 +117,31 @@ def get_random_bytes_list(rng: Random, length: int) -> bytes:
return bytes(rng.getrandbits(8) for _ in range(length))
def get_random_basic_value(rng: Random, typ) -> Any:
if is_bool_type(typ):
return rng.choice((True, False))
elif is_uint_type(typ):
size = uint_byte_size(typ)
assert size in UINT_SIZES
return rng.randint(0, 256**size - 1)
def get_random_basic_value(rng: Random, typ: BasicType) -> BasicValue:
if issubclass(typ, boolean):
return typ(rng.choice((True, False)))
elif issubclass(typ, uint):
assert typ.byte_len in UINT_BYTE_SIZES
return typ(rng.randint(0, 256 ** typ.byte_len - 1))
else:
raise ValueError(f"Not a basic type: typ={typ}")
def get_min_basic_value(typ) -> Any:
if is_bool_type(typ):
return False
elif is_uint_type(typ):
size = uint_byte_size(typ)
assert size in UINT_SIZES
return 0
def get_min_basic_value(typ: BasicType) -> BasicValue:
if issubclass(typ, boolean):
return typ(False)
elif issubclass(typ, uint):
assert typ.byte_len in UINT_BYTE_SIZES
return typ(0)
else:
raise ValueError(f"Not a basic type: typ={typ}")
def get_max_basic_value(typ) -> Any:
if is_bool_type(typ):
return True
elif is_uint_type(typ):
size = uint_byte_size(typ)
assert size in UINT_SIZES
return 256**size - 1
def get_max_basic_value(typ: BasicType) -> BasicValue:
if issubclass(typ, boolean):
return typ(True)
elif issubclass(typ, uint):
assert typ.byte_len in UINT_BYTE_SIZES
return typ(256 ** typ.byte_len - 1)
else:
raise ValueError(f"Not a basic type: typ={typ}")

View File

@ -0,0 +1,90 @@
from eth2spec.utils.ssz import ssz_typing as spec_ssz
import ssz
def translate_typ(typ) -> ssz.BaseSedes:
"""
Translates a spec type to a Py-SSZ type description (sedes).
:param typ: The spec type, a class.
:return: The Py-SSZ equivalent.
"""
if issubclass(typ, spec_ssz.Container):
return ssz.Container(
[translate_typ(field_typ) for field_name, field_typ in typ.get_fields().items()])
elif issubclass(typ, spec_ssz.BytesN):
return ssz.ByteVector(typ.length)
elif issubclass(typ, spec_ssz.Bytes):
return ssz.ByteList()
elif issubclass(typ, spec_ssz.Vector):
return ssz.Vector(translate_typ(typ.elem_type), typ.length)
elif issubclass(typ, spec_ssz.List):
# TODO: Make py-ssz List support the new fixed length list
return ssz.List(translate_typ(typ.elem_type))
elif issubclass(typ, spec_ssz.Bitlist):
# TODO: Once Bitlist implemented in py-ssz, use appropriate type
return ssz.List(translate_typ(typ.elem_type))
elif issubclass(typ, spec_ssz.Bitvector):
# TODO: Once Bitvector implemented in py-ssz, use appropriate type
return ssz.Vector(translate_typ(typ.elem_type), typ.length)
elif issubclass(typ, spec_ssz.boolean):
return ssz.boolean
elif issubclass(typ, spec_ssz.uint):
if typ.byte_len == 1:
return ssz.uint8
elif typ.byte_len == 2:
return ssz.uint16
elif typ.byte_len == 4:
return ssz.uint32
elif typ.byte_len == 8:
return ssz.uint64
elif typ.byte_len == 16:
return ssz.uint128
elif typ.byte_len == 32:
return ssz.uint256
else:
raise TypeError("invalid uint size")
else:
raise TypeError("Type not supported: {}".format(typ))
def translate_value(value, typ):
"""
Translate a value output from Py-SSZ deserialization into the given spec type.
:param value: The PySSZ value
:param typ: The type from the spec to translate into
:return: the translated value
"""
if issubclass(typ, spec_ssz.uint):
if typ.byte_len == 1:
return spec_ssz.uint8(value)
elif typ.byte_len == 2:
return spec_ssz.uint16(value)
elif typ.byte_len == 4:
return spec_ssz.uint32(value)
elif typ.byte_len == 8:
return spec_ssz.uint64(value)
elif typ.byte_len == 16:
return spec_ssz.uint128(value)
elif typ.byte_len == 32:
return spec_ssz.uint256(value)
else:
raise TypeError("invalid uint size")
elif issubclass(typ, spec_ssz.List):
return [translate_value(elem, typ.elem_type) for elem in value]
elif issubclass(typ, spec_ssz.boolean):
return value
elif issubclass(typ, spec_ssz.Vector):
return typ(*(translate_value(elem, typ.elem_type) for elem in value))
elif issubclass(typ, spec_ssz.Bitlist):
return typ(value)
elif issubclass(typ, spec_ssz.Bitvector):
return typ(value)
elif issubclass(typ, spec_ssz.BytesN):
return typ(value)
elif issubclass(typ, spec_ssz.Bytes):
return value
if issubclass(typ, spec_ssz.Container):
return typ(**{f_name: translate_value(f_val, f_typ) for (f_val, (f_name, f_typ))
in zip(value, typ.get_fields().items())})
else:
raise TypeError("Type not supported: {}".format(typ))

View File

@ -0,0 +1,35 @@
from eth2spec.fuzzing.decoder import translate_typ, translate_value
from eth2spec.phase0 import spec
from eth2spec.utils.ssz import ssz_impl as spec_ssz_impl
from random import Random
from eth2spec.debug import random_value
def test_decoder():
rng = Random(123)
# check these types only, Block covers a lot of operation types already.
# TODO: Once has Bitlists and Bitvectors, add back
# spec.BeaconState and spec.BeaconBlock
for typ in [spec.IndexedAttestation, spec.AttestationDataAndCustodyBit]:
# create a random pyspec value
original = random_value.get_random_ssz_object(rng, typ, 100, 10,
mode=random_value.RandomizationMode.mode_random,
chaos=True)
# serialize it, using pyspec
pyspec_data = spec_ssz_impl.serialize(original)
# get the py-ssz type for it
block_sedes = translate_typ(typ)
# try decoding using the py-ssz type
raw_value = block_sedes.deserialize(pyspec_data)
# serialize it using py-ssz
pyssz_data = block_sedes.serialize(raw_value)
# now check if the serialized form is equal. If so, we confirmed decoding and encoding to work.
assert pyspec_data == pyssz_data
# now translate the py-ssz value in a pyspec-value
block = translate_value(raw_value, typ)
# and see if the hash-tree-root of the original matches the hash-tree-root of the decoded & translated value.
assert spec_ssz_impl.hash_tree_root(original) == spec_ssz_impl.hash_tree_root(block)

View File

@ -27,9 +27,13 @@ def with_state(fn):
DEFAULT_BLS_ACTIVE = False
def spectest_with_bls_switch(fn):
return bls_switch(spectest()(fn))
# shorthand for decorating @with_state @spectest()
def spec_state_test(fn):
return with_state(bls_switch(spectest()(fn)))
return with_state(spectest_with_bls_switch(fn))
def expect_assertion_error(fn):

View File

@ -0,0 +1,118 @@
from eth2spec.test.context import with_all_phases, with_state, bls_switch
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.state import state_transition_and_sign_block
def add_block_to_store(spec, store, block):
pre_state = store.block_states[block.parent_root]
block_time = pre_state.genesis_time + block.slot * spec.SECONDS_PER_SLOT
if store.time < block_time:
spec.on_tick(store, block_time)
spec.on_block(store, block)
def add_attestation_to_store(spec, store, attestation):
parent_block = store.blocks[attestation.data.beacon_block_root]
pre_state = store.block_states[spec.signing_root(parent_block)]
block_time = pre_state.genesis_time + parent_block.slot * spec.SECONDS_PER_SLOT
next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.SECONDS_PER_SLOT
if store.time < next_epoch_time:
spec.on_tick(store, next_epoch_time)
spec.on_attestation(store, attestation)
@with_all_phases
@with_state
@bls_switch
def test_genesis(spec, state):
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.signing_root(genesis_block)
@with_all_phases
@with_state
@bls_switch
def test_chain_no_attestations(spec, state):
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.signing_root(genesis_block)
# On receiving a block of `GENESIS_SLOT + 1` slot
block_1 = build_empty_block_for_next_slot(spec, state)
state_transition_and_sign_block(spec, state, block_1)
add_block_to_store(spec, store, block_1)
# On receiving a block of next epoch
block_2 = build_empty_block_for_next_slot(spec, state)
state_transition_and_sign_block(spec, state, block_2)
add_block_to_store(spec, store, block_2)
assert spec.get_head(store) == spec.signing_root(block_2)
@with_all_phases
@with_state
@bls_switch
def test_split_tie_breaker_no_attestations(spec, state):
genesis_state = state.copy()
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.signing_root(genesis_block)
# block at slot 1
block_1_state = genesis_state.copy()
block_1 = build_empty_block_for_next_slot(spec, block_1_state)
state_transition_and_sign_block(spec, block_1_state, block_1)
add_block_to_store(spec, store, block_1)
# additional block at slot 1
block_2_state = genesis_state.copy()
block_2 = build_empty_block_for_next_slot(spec, block_2_state)
block_2.body.graffiti = b'\x42' * 32
state_transition_and_sign_block(spec, block_2_state, block_2)
add_block_to_store(spec, store, block_2)
highest_root = max(spec.signing_root(block_1), spec.signing_root(block_2))
assert spec.get_head(store) == highest_root
@with_all_phases
@with_state
@bls_switch
def test_shorter_chain_but_heavier_weight(spec, state):
genesis_state = state.copy()
# Initialization
store = spec.get_genesis_store(state)
genesis_block = spec.BeaconBlock(state_root=state.hash_tree_root())
assert spec.get_head(store) == spec.signing_root(genesis_block)
# build longer tree
long_state = genesis_state.copy()
for i in range(3):
long_block = build_empty_block_for_next_slot(spec, long_state)
state_transition_and_sign_block(spec, long_state, long_block)
add_block_to_store(spec, store, long_block)
# build short tree
short_state = genesis_state.copy()
short_block = build_empty_block_for_next_slot(spec, short_state)
short_block.body.graffiti = b'\x42' * 32
state_transition_and_sign_block(spec, short_state, short_block)
add_block_to_store(spec, store, short_block)
short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True)
add_attestation_to_store(spec, store, short_attestation)
assert spec.get_head(store) == spec.signing_root(short_block)

View File

@ -0,0 +1,122 @@
from eth2spec.test.context import with_all_phases, with_state, bls_switch
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.state import next_slot
def run_on_attestation(spec, state, store, attestation, valid=True):
if not valid:
try:
spec.on_attestation(store, attestation)
except AssertionError:
return
else:
assert False
indexed_attestation = spec.get_indexed_attestation(state, attestation)
spec.on_attestation(store, attestation)
assert (
store.latest_messages[indexed_attestation.custody_bit_0_indices[0]] ==
spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
)
)
@with_all_phases
@with_state
@bls_switch
def test_on_attestation(spec, state):
store = spec.get_genesis_store(state)
time = 100
spec.on_tick(store, time)
block = build_empty_block_for_next_slot(spec, state, signed=True)
# store block in store
spec.on_block(store, block)
next_slot(spec, state)
attestation = get_valid_attestation(spec, state, slot=block.slot)
run_on_attestation(spec, state, store, attestation)
@with_all_phases
@with_state
@bls_switch
def test_on_attestation_target_not_in_store(spec, state):
store = spec.get_genesis_store(state)
time = 100
spec.on_tick(store, time)
# move to next epoch to make block new target
state.slot += spec.SLOTS_PER_EPOCH
block = build_empty_block_for_next_slot(spec, state, signed=True)
# do not add block to store
next_slot(spec, state)
attestation = get_valid_attestation(spec, state, slot=block.slot)
run_on_attestation(spec, state, store, attestation, False)
@with_all_phases
@with_state
@bls_switch
def test_on_attestation_future_epoch(spec, state):
store = spec.get_genesis_store(state)
time = 3 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
block = build_empty_block_for_next_slot(spec, state, signed=True)
# store block in store
spec.on_block(store, block)
next_slot(spec, state)
# move state forward but not store
attestation_slot = block.slot + spec.SLOTS_PER_EPOCH
state.slot = attestation_slot
attestation = get_valid_attestation(spec, state, slot=state.slot)
run_on_attestation(spec, state, store, attestation, False)
@with_all_phases
@with_state
@bls_switch
def test_on_attestation_same_slot(spec, state):
store = spec.get_genesis_store(state)
time = 1 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
block = build_empty_block_for_next_slot(spec, state, signed=True)
spec.on_block(store, block)
next_slot(spec, state)
attestation = get_valid_attestation(spec, state, slot=block.slot)
run_on_attestation(spec, state, store, attestation, False)
@with_all_phases
@with_state
@bls_switch
def test_on_attestation_invalid_attestation(spec, state):
store = spec.get_genesis_store(state)
time = 3 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
block = build_empty_block_for_next_slot(spec, state, signed=True)
spec.on_block(store, block)
next_slot(spec, state)
attestation = get_valid_attestation(spec, state, slot=block.slot)
# make attestation invalid
attestation.custody_bits[0:8] = [0, 0, 0, 0, 1, 1, 1, 1]
run_on_attestation(spec, state, store, attestation, False)

View File

@ -0,0 +1,127 @@
from eth2spec.utils.ssz.ssz_impl import signing_root
from eth2spec.test.context import with_all_phases, with_state, bls_switch
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.state import next_epoch, next_epoch_with_attestations
def run_on_block(spec, state, store, block, valid=True):
if not valid:
try:
spec.on_block(store, block)
except AssertionError:
return
else:
assert False
spec.on_block(store, block)
assert store.blocks[signing_root(block)] == block
def apply_next_epoch_with_attestations(spec, state, store):
_, new_blocks, state = next_epoch_with_attestations(spec, state, True, False)
for block in new_blocks:
block_root = signing_root(block)
store.blocks[block_root] = block
store.block_states[block_root] = state
last_block = block
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
return state, store, last_block
@with_all_phases
@with_state
@bls_switch
def test_basic(spec, state):
# Initialization
store = spec.get_genesis_store(state)
time = 100
spec.on_tick(store, time)
assert store.time == time
# On receiving a block of `GENESIS_SLOT + 1` slot
block = build_empty_block_for_next_slot(spec, state)
run_on_block(spec, state, store, block)
# On receiving a block of next epoch
store.time = time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
block = build_empty_block_for_next_slot(spec, state)
block.slot += spec.SLOTS_PER_EPOCH
run_on_block(spec, state, store, block)
# TODO: add tests for justified_root and finalized_root
@with_all_phases
@with_state
@bls_switch
def test_on_block_checkpoints(spec, state):
# Initialization
store = spec.get_genesis_store(state)
time = 100
spec.on_tick(store, time)
next_epoch(spec, state)
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
state, store, last_block = apply_next_epoch_with_attestations(spec, state, store)
next_epoch(spec, state)
spec.on_tick(store, store.time + state.slot * spec.SECONDS_PER_SLOT)
last_block_root = signing_root(last_block)
# Mock the finalized_checkpoint
store.block_states[last_block_root].finalized_checkpoint = (
store.block_states[last_block_root].current_justified_checkpoint
)
# On receiving a block of `GENESIS_SLOT + 1` slot
block = build_empty_block_for_next_slot(spec, state)
run_on_block(spec, state, store, block)
@with_all_phases
@with_state
@bls_switch
def test_on_block_future_block(spec, state):
# Initialization
store = spec.get_genesis_store(state)
# do not tick time
# Fail receiving block of `GENESIS_SLOT + 1` slot
block = build_empty_block_for_next_slot(spec, state)
run_on_block(spec, state, store, block, False)
@with_all_phases
@with_state
@bls_switch
def test_on_block_bad_parent_root(spec, state):
# Initialization
store = spec.get_genesis_store(state)
time = 100
spec.on_tick(store, time)
# Fail receiving block of `GENESIS_SLOT + 1` slot
block = build_empty_block_for_next_slot(spec, state)
block.parent_root = b'\x45' * 32
run_on_block(spec, state, store, block, False)
@with_all_phases
@with_state
@bls_switch
def test_on_block_before_finalized(spec, state):
# Initialization
store = spec.get_genesis_store(state)
time = 100
spec.on_tick(store, time)
store.finalized_checkpoint = spec.Checkpoint(
epoch=store.finalized_checkpoint.epoch + 2,
root=store.finalized_checkpoint.root
)
# Fail receiving block of `GENESIS_SLOT + 1` slot
block = build_empty_block_for_next_slot(spec, state)
run_on_block(spec, state, store, block, False)

View File

@ -0,0 +1,30 @@
from eth2spec.test.context import spectest_with_bls_switch, with_phases
from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits,
)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_initialize_beacon_state_from_eth1(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
deposits, deposit_root = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
yield 'eth1_block_hash', eth1_block_hash
yield 'eth1_timestamp', eth1_timestamp
yield 'deposits', deposits
# initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.SECONDS_PER_DAY + 2 * spec.SECONDS_PER_DAY
assert len(state.validators) == deposit_count
assert state.eth1_data.deposit_root == deposit_root
assert state.eth1_data.deposit_count == deposit_count
assert state.eth1_data.block_hash == eth1_block_hash
# yield state
yield 'state', state

View File

@ -0,0 +1,87 @@
from eth2spec.test.context import spectest_with_bls_switch, with_phases
from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits,
)
def create_valid_beacon_state(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
deposits, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
return spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
def run_is_valid_genesis_state(spec, state, valid=True):
"""
Run ``is_valid_genesis_state``, yielding:
- genesis ('state')
- is_valid ('is_valid')
"""
yield 'genesis', state
is_valid = spec.is_valid_genesis_state(state)
yield 'is_valid', is_valid
assert is_valid == valid
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_true(spec):
state = create_valid_beacon_state(spec)
yield from run_is_valid_genesis_state(spec, state, valid=True)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_false_invalid_timestamp(spec):
state = create_valid_beacon_state(spec)
state.genesis_time = spec.MIN_GENESIS_TIME - 1
yield from run_is_valid_genesis_state(spec, state, valid=False)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_true_more_balance(spec):
state = create_valid_beacon_state(spec)
state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE + 1
yield from run_is_valid_genesis_state(spec, state, valid=True)
# TODO: not part of the genesis function yet. Erroneously merged.
# @with_phases(['phase0'])
# @spectest_with_bls_switch
# def test_is_valid_genesis_state_false_not_enough_balance(spec):
# state = create_valid_beacon_state(spec)
# state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1
#
# yield from run_is_valid_genesis_state(spec, state, valid=False)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_true_one_more_validator(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1
deposits, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
yield from run_is_valid_genesis_state(spec, state, valid=True)
@with_phases(['phase0'])
@spectest_with_bls_switch
def test_is_valid_genesis_state_false_not_enough_validator(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1
deposits, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True)
eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
yield from run_is_valid_genesis_state(spec, state, valid=False)

View File

@ -1,10 +1,10 @@
from typing import List
from eth2spec.test.helpers.bitfields import set_bitfield_bit
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import Bitlist
def build_attestation_data(spec, state, slot, shard):
@ -15,7 +15,7 @@ def build_attestation_data(spec, state, slot, shard):
else:
block_root = spec.get_block_root_at_slot(state, slot)
current_epoch_start_slot = spec.get_epoch_start_slot(spec.get_current_epoch(state))
current_epoch_start_slot = spec.compute_start_slot_of_epoch(spec.get_current_epoch(state))
if slot < current_epoch_start_slot:
epoch_boundary_root = spec.get_block_root(state, spec.get_previous_epoch(state))
elif slot == current_epoch_start_slot:
@ -24,28 +24,26 @@ def build_attestation_data(spec, state, slot, shard):
epoch_boundary_root = spec.get_block_root(state, spec.get_current_epoch(state))
if slot < current_epoch_start_slot:
justified_epoch = state.previous_justified_epoch
justified_block_root = state.previous_justified_root
source_epoch = state.previous_justified_checkpoint.epoch
source_root = state.previous_justified_checkpoint.root
else:
justified_epoch = state.current_justified_epoch
justified_block_root = state.current_justified_root
source_epoch = state.current_justified_checkpoint.epoch
source_root = state.current_justified_checkpoint.root
if spec.slot_to_epoch(slot) == spec.get_current_epoch(state):
if spec.compute_epoch_of_slot(slot) == spec.get_current_epoch(state):
parent_crosslink = state.current_crosslinks[shard]
else:
parent_crosslink = state.previous_crosslinks[shard]
return spec.AttestationData(
beacon_block_root=block_root,
source_epoch=justified_epoch,
source_root=justified_block_root,
target_epoch=spec.slot_to_epoch(slot),
target_root=epoch_boundary_root,
source=spec.Checkpoint(epoch=source_epoch, root=source_root),
target=spec.Checkpoint(epoch=spec.compute_epoch_of_slot(slot), root=epoch_boundary_root),
crosslink=spec.Crosslink(
shard=shard,
start_epoch=parent_crosslink.end_epoch,
end_epoch=min(spec.slot_to_epoch(slot), parent_crosslink.end_epoch + spec.MAX_EPOCHS_PER_CROSSLINK),
data_root=spec.ZERO_HASH,
end_epoch=min(spec.compute_epoch_of_slot(slot), parent_crosslink.end_epoch + spec.MAX_EPOCHS_PER_CROSSLINK),
data_root=spec.Hash(),
parent_root=hash_tree_root(parent_crosslink),
),
)
@ -55,27 +53,26 @@ def get_valid_attestation(spec, state, slot=None, signed=False):
if slot is None:
slot = state.slot
epoch = spec.slot_to_epoch(slot)
epoch_start_shard = spec.get_epoch_start_shard(state, epoch)
committees_per_slot = spec.get_epoch_committee_count(state, epoch) // spec.SLOTS_PER_EPOCH
epoch = spec.compute_epoch_of_slot(slot)
epoch_start_shard = spec.get_start_shard(state, epoch)
committees_per_slot = spec.get_committee_count(state, epoch) // spec.SLOTS_PER_EPOCH
shard = (epoch_start_shard + committees_per_slot * (slot % spec.SLOTS_PER_EPOCH)) % spec.SHARD_COUNT
attestation_data = build_attestation_data(spec, state, slot, shard)
crosslink_committee = spec.get_crosslink_committee(
state,
attestation_data.target_epoch,
attestation_data.crosslink.shard
attestation_data.target.epoch,
attestation_data.crosslink.shard,
)
committee_size = len(crosslink_committee)
bitfield_length = (committee_size + 7) // 8
aggregation_bitfield = b'\x00' * bitfield_length
custody_bitfield = b'\x00' * bitfield_length
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
custody_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
attestation = spec.Attestation(
aggregation_bitfield=aggregation_bitfield,
aggregation_bits=aggregation_bits,
data=attestation_data,
custody_bitfield=custody_bitfield,
custody_bits=custody_bits,
)
fill_aggregate_attestation(spec, state, attestation)
if signed:
@ -108,7 +105,7 @@ def sign_attestation(spec, state, attestation):
participants = spec.get_attesting_indices(
state,
attestation.data,
attestation.aggregation_bitfield,
attestation.aggregation_bits,
)
attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, participants)
@ -126,7 +123,7 @@ def get_attestation_signature(spec, state, attestation_data, privkey, custody_bi
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_ATTESTATION,
message_epoch=attestation_data.target_epoch,
message_epoch=attestation_data.target.epoch,
)
)
@ -134,11 +131,11 @@ def get_attestation_signature(spec, state, attestation_data, privkey, custody_bi
def fill_aggregate_attestation(spec, state, attestation):
crosslink_committee = spec.get_crosslink_committee(
state,
attestation.data.target_epoch,
attestation.data.target.epoch,
attestation.data.crosslink.shard,
)
for i in range(len(crosslink_committee)):
attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)
attestation.aggregation_bits[i] = True
def add_attestation_to_state(spec, state, attestation, slot):

View File

@ -7,12 +7,12 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False):
attestation_1 = get_valid_attestation(spec, state, signed=signed_1)
attestation_2 = deepcopy(attestation_1)
attestation_2.data.target_root = b'\x01' * 32
attestation_2.data.target.root = b'\x01' * 32
if signed_2:
sign_attestation(spec, state, attestation_2)
return spec.AttesterSlashing(
attestation_1=spec.convert_to_indexed(state, attestation_1),
attestation_2=spec.convert_to_indexed(state, attestation_2),
attestation_1=spec.get_indexed_attestation(state, attestation_1),
attestation_2=spec.get_indexed_attestation(state, attestation_2),
)

View File

@ -1,11 +0,0 @@
def set_bitfield_bit(bitfield, i):
"""
Set the bit in ``bitfield`` at position ``i`` to ``1``.
"""
byte_index = i // 8
bit_index = i % 8
return (
bitfield[:byte_index] +
bytes([bitfield[byte_index] | (1 << bit_index)]) +
bitfield[byte_index + 1:]
)

View File

@ -14,7 +14,7 @@ def sign_block(spec, state, block, proposer_index=None):
if block.slot == state.slot:
proposer_index = spec.get_beacon_proposer_index(state)
else:
if spec.slot_to_epoch(state.slot) + 1 > spec.slot_to_epoch(block.slot):
if spec.compute_epoch_of_slot(state.slot) + 1 > spec.compute_epoch_of_slot(block.slot):
print("warning: block slot far away, and no proposer index manually given."
" Signing block is slow due to transition for proposer index calculation.")
# use stub state to get proposer index of future slot
@ -26,10 +26,10 @@ def sign_block(spec, state, block, proposer_index=None):
block.body.randao_reveal = bls_sign(
privkey=privkey,
message_hash=hash_tree_root(spec.slot_to_epoch(block.slot)),
message_hash=hash_tree_root(spec.compute_epoch_of_slot(block.slot)),
domain=spec.get_domain(
state,
message_epoch=spec.slot_to_epoch(block.slot),
message_epoch=spec.compute_epoch_of_slot(block.slot),
domain_type=spec.DOMAIN_RANDAO,
)
)
@ -39,7 +39,7 @@ def sign_block(spec, state, block, proposer_index=None):
domain=spec.get_domain(
state,
spec.DOMAIN_BEACON_PROPOSER,
spec.slot_to_epoch(block.slot)))
spec.compute_epoch_of_slot(block.slot)))
def apply_empty_block(spec, state):
@ -57,9 +57,9 @@ def build_empty_block(spec, state, slot=None, signed=False):
slot = state.slot
empty_block = spec.BeaconBlock()
empty_block.slot = slot
empty_block.body.eth1_data.deposit_count = state.deposit_index
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
previous_block_header = deepcopy(state.latest_block_header)
if previous_block_header.state_root == spec.ZERO_HASH:
if previous_block_header.state_root == spec.Hash():
previous_block_header.state_root = state.hash_tree_root()
empty_block.parent_root = signing_root(previous_block_header)

View File

@ -1,5 +1,6 @@
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import bls_sign
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
from eth2spec.utils.hash_function import hash
def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
@ -10,8 +11,9 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
if epoch is None:
epoch = current_epoch + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING
# Generate the secret that is being revealed
reveal = bls_sign(
message_hash=spec.hash_tree_root(epoch),
message_hash=spec.hash_tree_root(spec.Epoch(epoch)),
privkey=privkeys[revealed_index],
domain=spec.get_domain(
state=state,
@ -19,8 +21,11 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
message_epoch=epoch,
),
)
mask = bls_sign(
message_hash=spec.hash_tree_root(epoch),
# Generate the mask (any random 32 bytes that don't reveal the masker's secret will do)
mask = hash(reveal)
# Generate masker's signature on the mask
masker_signature = bls_sign(
message_hash=mask,
privkey=privkeys[masker_index],
domain=spec.get_domain(
state=state,
@ -28,11 +33,12 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
message_epoch=epoch,
),
)
masked_reveal = bls_aggregate_signatures([reveal, masker_signature])
return spec.EarlyDerivedSecretReveal(
revealed_index=revealed_index,
epoch=epoch,
reveal=reveal,
reveal=masked_reveal,
masker_index=masker_index,
mask=mask,
)

View File

@ -1,78 +1,100 @@
from eth2spec.test.helpers.keys import pubkeys, privkeys
from eth2spec.utils.bls import bls_sign
from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_root, get_merkle_proof
from eth2spec.utils.ssz.ssz_impl import signing_root
from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof
from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root
from eth2spec.utils.ssz.ssz_typing import List
def build_deposit_data(spec, state, pubkey, privkey, amount, withdrawal_credentials, signed=False):
def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=None, signed=False):
deposit_data = spec.DepositData(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
amount=amount,
)
if signed:
sign_deposit_data(spec, state, deposit_data, privkey)
sign_deposit_data(spec, deposit_data, privkey, state)
return deposit_data
def sign_deposit_data(spec, state, deposit_data, privkey):
signature = bls_sign(
message_hash=signing_root(deposit_data),
privkey=privkey,
domain=spec.get_domain(
def sign_deposit_data(spec, deposit_data, privkey, state=None):
if state is None:
# Genesis
domain = spec.compute_domain(spec.DOMAIN_DEPOSIT)
else:
domain = spec.get_domain(
state,
spec.DOMAIN_DEPOSIT,
)
signature = bls_sign(
message_hash=signing_root(deposit_data),
privkey=privkey,
domain=domain,
)
deposit_data.signature = signature
def build_deposit(spec,
state,
deposit_data_leaves,
deposit_data_list,
pubkey,
privkey,
amount,
withdrawal_credentials,
signed):
deposit_data = build_deposit_data(spec, state, pubkey, privkey, amount, withdrawal_credentials, signed)
deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=state, signed=signed)
index = len(deposit_data_list)
deposit_data_list.append(deposit_data)
root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list))
tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list]))
proof = list(get_merkle_proof(tree, item_index=index)) + [(index + 1).to_bytes(32, 'little')]
leaf = deposit_data.hash_tree_root()
assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root)
deposit = spec.Deposit(proof=proof, data=deposit_data)
item = deposit_data.hash_tree_root()
index = len(deposit_data_leaves)
deposit_data_leaves.append(item)
tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
root = get_merkle_root((tuple(deposit_data_leaves)))
proof = list(get_merkle_proof(tree, item_index=index))
assert spec.verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
return deposit, root, deposit_data_list
deposit = spec.Deposit(
proof=list(proof),
index=index,
data=deposit_data,
def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False):
deposit_data_list = []
genesis_deposits = []
for validator_index in range(genesis_validator_count):
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
# insecurely use pubkey as withdrawal key if no credentials provided
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
deposit, root, deposit_data_list = build_deposit(
spec,
None,
deposit_data_list,
pubkey,
privkey,
amount,
withdrawal_credentials,
signed,
)
genesis_deposits.append(deposit)
return deposit, root, deposit_data_leaves
return genesis_deposits, root
def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False):
"""
Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount.
"""
pre_validator_count = len(state.validator_registry)
# fill previous deposits with zero-hash
deposit_data_leaves = [spec.ZERO_HASH] * pre_validator_count
deposit_data_list = []
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
# insecurely use pubkey as withdrawal key if no credentials provided
if withdrawal_credentials is None:
withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(pubkey)[1:]
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
deposit, root, deposit_data_leaves = build_deposit(
deposit, root, deposit_data_list = build_deposit(
spec,
state,
deposit_data_leaves,
deposit_data_list,
pubkey,
privkey,
amount,
@ -80,6 +102,7 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
signed,
)
state.latest_eth1_data.deposit_root = root
state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
state.eth1_deposit_index = 0
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count = len(deposit_data_list)
return deposit

View File

@ -1,11 +1,12 @@
from eth2spec.test.helpers.keys import pubkeys
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import List
def build_mock_validator(spec, i: int, balance: int):
pubkey = pubkeys[i]
# insecurely use pubkey as withdrawal key as well
withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(pubkey)[1:]
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
return spec.Validator(
pubkey=pubkeys[i],
withdrawal_credentials=withdrawal_credentials,
@ -22,26 +23,32 @@ def create_genesis_state(spec, num_validators):
state = spec.BeaconState(
genesis_time=0,
deposit_index=num_validators,
latest_eth1_data=spec.Eth1Data(
eth1_deposit_index=num_validators,
eth1_data=spec.Eth1Data(
deposit_root=deposit_root,
deposit_count=num_validators,
block_hash=spec.ZERO_HASH,
))
block_hash=spec.Hash(),
),
latest_block_header=spec.BeaconBlockHeader(body_root=spec.hash_tree_root(spec.BeaconBlockBody())),
)
# We "hack" in the initial validators,
# as it is much faster than creating and processing genesis deposits for every single test case.
state.balances = [spec.MAX_EFFECTIVE_BALANCE] * num_validators
state.validator_registry = [build_mock_validator(spec, i, state.balances[i]) for i in range(num_validators)]
state.validators = [build_mock_validator(spec, i, state.balances[i]) for i in range(num_validators)]
# Process genesis activations
for validator in state.validator_registry:
for validator in state.validators:
if validator.effective_balance >= spec.MAX_EFFECTIVE_BALANCE:
validator.activation_eligibility_epoch = spec.GENESIS_EPOCH
validator.activation_epoch = spec.GENESIS_EPOCH
genesis_active_index_root = hash_tree_root(spec.get_active_validator_indices(state, spec.GENESIS_EPOCH))
for index in range(spec.LATEST_ACTIVE_INDEX_ROOTS_LENGTH):
state.latest_active_index_roots[index] = genesis_active_index_root
genesis_active_index_root = hash_tree_root(List[spec.ValidatorIndex, spec.VALIDATOR_REGISTRY_LIMIT](
spec.get_active_validator_indices(state, spec.GENESIS_EPOCH)))
genesis_compact_committees_root = hash_tree_root(List[spec.ValidatorIndex, spec.VALIDATOR_REGISTRY_LIMIT](
spec.get_active_validator_indices(state, spec.GENESIS_EPOCH)))
for index in range(spec.EPOCHS_PER_HISTORICAL_VECTOR):
state.active_index_roots[index] = genesis_active_index_root
state.compact_committees_roots[index] = genesis_compact_committees_root
return state

View File

@ -0,0 +1,47 @@
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import (
bls_sign,
only_with_bls,
)
from eth2spec.utils.ssz.ssz_impl import (
signing_root,
)
@only_with_bls()
def sign_shard_block(spec, state, block, shard, proposer_index=None):
if proposer_index is None:
proposer_index = spec.get_shard_block_proposer_index(state, shard, block.core.slot)
privkey = privkeys[proposer_index]
block.signatures.proposer_signature = bls_sign(
message_hash=signing_root(block),
privkey=privkey,
domain=spec.get_domain(
state,
spec.DOMAIN_SHARD_PROPOSER,
spec.compute_epoch_of_shard_slot(block.core.slot),
)
)
def build_empty_shard_block(spec, state, slot, shard, parent_root, signed=False):
if slot is None:
slot = state.slot
block = spec.ShardBlock(
core=spec.ExtendedShardBlockCore(
slot=slot,
beacon_chain_root=state.block_roots[state.slot % spec.SLOTS_PER_HISTORICAL_ROOT],
parent_root=parent_root,
),
signatures=spec.ShardBlockSignatures(
attestation_signature=b'\x12' * 96,
proposer_signature=b'\x25' * 96,
)
)
if signed:
sign_shard_block(spec, state, block, shard)
return block

View File

@ -7,7 +7,7 @@ from eth2spec.test.helpers.keys import pubkey_to_privkey
def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[-1]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
slot = state.slot
header_1 = spec.BeaconBlockHeader(

View File

@ -1,3 +1,8 @@
from copy import deepcopy
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.block import sign_block, build_empty_block_for_next_slot
def get_balance(state, index):
return state.balances[index]
@ -22,4 +27,41 @@ def get_state_root(spec, state, slot) -> bytes:
Return the state root at a recent ``slot``.
"""
assert slot < state.slot <= slot + spec.SLOTS_PER_HISTORICAL_ROOT
return state.latest_state_roots[slot % spec.SLOTS_PER_HISTORICAL_ROOT]
return state.state_roots[slot % spec.SLOTS_PER_HISTORICAL_ROOT]
def state_transition_and_sign_block(spec, state, block):
"""
State transition via the provided ``block``
then package the block with the state root and signature.
"""
spec.state_transition(state, block)
block.state_root = state.hash_tree_root()
sign_block(spec, state, block)
def next_epoch_with_attestations(spec,
state,
fill_cur_epoch,
fill_prev_epoch):
assert state.slot % spec.SLOTS_PER_EPOCH == 0
post_state = deepcopy(state)
blocks = []
for _ in range(spec.SLOTS_PER_EPOCH):
block = build_empty_block_for_next_slot(spec, post_state)
if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
if slot_to_attest >= spec.compute_start_slot_of_epoch(spec.get_current_epoch(post_state)):
cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest)
block.body.attestations.append(cur_attestation)
if fill_prev_epoch:
slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
prev_attestation = get_valid_attestation(spec, post_state, slot_to_attest)
block.body.attestations.append(prev_attestation)
state_transition_and_sign_block(spec, post_state, block)
blocks.append(block)
return state, blocks, post_state

View File

@ -4,12 +4,14 @@ from eth2spec.utils.bls import bls_sign
from eth2spec.utils.ssz.ssz_impl import signing_root
def get_valid_transfer(spec, state, slot=None, sender_index=None, amount=None, fee=None, signed=False):
def get_valid_transfer(spec, state, slot=None, sender_index=None,
recipient_index=None, amount=None, fee=None, signed=False):
if slot is None:
slot = state.slot
current_epoch = spec.get_current_epoch(state)
if sender_index is None:
sender_index = spec.get_active_validator_indices(state, current_epoch)[-1]
if recipient_index is None:
recipient_index = spec.get_active_validator_indices(state, current_epoch)[0]
transfer_pubkey = pubkeys[-1]
transfer_privkey = privkeys[-1]
@ -31,8 +33,8 @@ def get_valid_transfer(spec, state, slot=None, sender_index=None, amount=None, f
sign_transfer(spec, state, transfer, transfer_privkey)
# ensure withdrawal_credentials reproducible
state.validator_registry[transfer.sender].withdrawal_credentials = (
spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(transfer.pubkey)[1:]
state.validators[transfer.sender].withdrawal_credentials = (
spec.BLS_WITHDRAWAL_PREFIX + spec.hash(transfer.pubkey)[1:]
)
return transfer

View File

@ -1,8 +1,7 @@
from copy import deepcopy
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
from eth2spec.test.helpers.attestations import (
get_valid_attestation,
sign_aggregate_attestation,
sign_attestation,
)
from eth2spec.test.helpers.state import (
@ -10,6 +9,7 @@ from eth2spec.test.helpers.state import (
next_slot,
)
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.utils.ssz.ssz_typing import Bitlist
def run_attestation_processing(spec, state, attestation, valid=True):
@ -38,7 +38,7 @@ def run_attestation_processing(spec, state, attestation, valid=True):
spec.process_attestation(state, attestation)
# Make sure the attestation has been processed
if attestation.data.target_epoch == spec.get_current_epoch(state):
if attestation.data.target.epoch == spec.get_current_epoch(state):
assert len(state.current_epoch_attestations) == current_epoch_count + 1
else:
assert len(state.previous_epoch_attestations) == previous_epoch_count + 1
@ -60,6 +60,7 @@ def test_success(spec, state):
@spec_state_test
def test_success_previous_epoch(spec, state):
attestation = get_valid_attestation(spec, state, signed=True)
state.slot = spec.SLOTS_PER_EPOCH - 1
next_epoch(spec, state)
apply_empty_block(spec, state)
@ -69,6 +70,9 @@ def test_success_previous_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_success_since_max_epochs_per_crosslink(spec, state):
# Do not run mainnet (64 epochs), that would mean the equivalent of ~7 hours chain simulation.
if spec.MAX_EPOCHS_PER_CROSSLINK > 4:
return
for _ in range(spec.MAX_EPOCHS_PER_CROSSLINK + 2):
next_epoch(spec, state)
apply_empty_block(spec, state)
@ -85,6 +89,32 @@ def test_success_since_max_epochs_per_crosslink(spec, state):
yield from run_attestation_processing(spec, state, attestation)
@with_all_phases
@spec_state_test
def test_wrong_end_epoch_with_max_epochs_per_crosslink(spec, state):
# Do not run mainnet (64 epochs), that would mean the equivalent of ~7 hours chain simulation.
if spec.MAX_EPOCHS_PER_CROSSLINK > 4:
return
for _ in range(spec.MAX_EPOCHS_PER_CROSSLINK + 2):
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state)
data = attestation.data
# test logic sanity check: make sure the attestation only includes MAX_EPOCHS_PER_CROSSLINK epochs
assert data.crosslink.end_epoch - data.crosslink.start_epoch == spec.MAX_EPOCHS_PER_CROSSLINK
# Now change it to be different
data.crosslink.end_epoch += 1
sign_attestation(spec, state, attestation)
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
next_slot(spec, state)
apply_empty_block(spec, state)
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@always_bls
@spec_state_test
@ -108,8 +138,9 @@ def test_before_inclusion_delay(spec, state):
@spec_state_test
def test_after_epoch_slots(spec, state):
attestation = get_valid_attestation(spec, state, signed=True)
state.slot = spec.SLOTS_PER_EPOCH - 1
# increment past latest inclusion slot
spec.process_slots(state, state.slot + spec.SLOTS_PER_EPOCH + 1)
spec.process_slots(state, state.slot + 2)
apply_empty_block(spec, state)
yield from run_attestation_processing(spec, state, attestation, False)
@ -119,16 +150,16 @@ def test_after_epoch_slots(spec, state):
@spec_state_test
def test_old_source_epoch(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 5
state.finalized_epoch = 2
state.previous_justified_epoch = 3
state.current_justified_epoch = 4
state.finalized_checkpoint.epoch = 2
state.previous_justified_checkpoint.epoch = 3
state.current_justified_checkpoint.epoch = 4
attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
# test logic sanity check: make sure the attestation is pointing to oldest known source epoch
assert attestation.data.source_epoch == state.previous_justified_epoch
assert attestation.data.source.epoch == state.previous_justified_checkpoint.epoch
# Now go beyond that, it will be invalid
attestation.data.source_epoch -= 1
attestation.data.source.epoch -= 1
sign_attestation(spec, state, attestation)
@ -148,13 +179,61 @@ def test_wrong_shard(spec, state):
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
def test_invalid_shard(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
# off by one (with respect to valid range) on purpose
attestation.data.crosslink.shard = spec.SHARD_COUNT
sign_attestation(spec, state, attestation)
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
def test_old_target_epoch(spec, state):
assert spec.MIN_ATTESTATION_INCLUSION_DELAY < spec.SLOTS_PER_EPOCH * 2
attestation = get_valid_attestation(spec, state, signed=True)
state.slot = spec.SLOTS_PER_EPOCH * 2 # target epoch will be too old to handle
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
def test_future_target_epoch(spec, state):
assert spec.MIN_ATTESTATION_INCLUSION_DELAY < spec.SLOTS_PER_EPOCH * 2
attestation = get_valid_attestation(spec, state)
participants = spec.get_attesting_indices(
state,
attestation.data,
attestation.aggregation_bits
)
attestation.data.target.epoch = spec.get_current_epoch(state) + 1 # target epoch will be too new to handle
# manually add signature for correct participants
attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, participants)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
def test_new_source_epoch(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation.data.source_epoch += 1
attestation.data.source.epoch += 1
sign_attestation(spec, state, attestation)
@ -167,7 +246,7 @@ def test_source_root_is_target_root(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation.data.source_root = attestation.data.target_root
attestation.data.source.root = attestation.data.target.root
sign_attestation(spec, state, attestation)
@ -178,23 +257,20 @@ def test_source_root_is_target_root(spec, state):
@spec_state_test
def test_invalid_current_source_root(spec, state):
state.slot = spec.SLOTS_PER_EPOCH * 5
state.finalized_epoch = 2
state.finalized_checkpoint.epoch = 2
state.previous_justified_epoch = 3
state.previous_justified_root = b'\x01' * 32
state.current_justified_epoch = 4
state.current_justified_root = b'\xff' * 32
state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b'\x01' * 32)
state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b'\x32' * 32)
attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
# Test logic sanity checks:
assert state.current_justified_root != state.previous_justified_root
assert attestation.data.source_root == state.previous_justified_root
assert state.current_justified_checkpoint.root != state.previous_justified_checkpoint.root
assert attestation.data.source.root == state.previous_justified_checkpoint.root
# Make attestation source root invalid: should be previous justified, not current one
attestation.data.source_root = state.current_justified_root
attestation.data.source.root = state.current_justified_checkpoint.root
sign_attestation(spec, state, attestation)
@ -207,7 +283,7 @@ def test_bad_source_root(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation.data.source_root = b'\x42' * 32
attestation.data.source.root = b'\x42' * 32
sign_attestation(spec, state, attestation)
@ -230,15 +306,17 @@ def test_non_zero_crosslink_data_root(spec, state):
@with_all_phases
@spec_state_test
def test_bad_parent_crosslink(spec, state):
state.slot = spec.SLOTS_PER_EPOCH - 1
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
attestation = get_valid_attestation(spec, state, signed=False)
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
next_slot(spec, state)
apply_empty_block(spec, state)
attestation.data.crosslink.parent_root = b'\x27' * 32
sign_attestation(spec, state, attestation)
yield from run_attestation_processing(spec, state, attestation, False)
@ -246,15 +324,17 @@ def test_bad_parent_crosslink(spec, state):
@with_all_phases
@spec_state_test
def test_bad_crosslink_start_epoch(spec, state):
state.slot = spec.SLOTS_PER_EPOCH - 1
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
attestation = get_valid_attestation(spec, state, signed=False)
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
next_slot(spec, state)
apply_empty_block(spec, state)
attestation.data.crosslink.start_epoch += 1
sign_attestation(spec, state, attestation)
yield from run_attestation_processing(spec, state, attestation, False)
@ -262,26 +342,31 @@ def test_bad_crosslink_start_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_bad_crosslink_end_epoch(spec, state):
state.slot = spec.SLOTS_PER_EPOCH - 1
next_epoch(spec, state)
apply_empty_block(spec, state)
attestation = get_valid_attestation(spec, state, signed=True)
attestation = get_valid_attestation(spec, state, signed=False)
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
next_slot(spec, state)
apply_empty_block(spec, state)
attestation.data.crosslink.end_epoch += 1
sign_attestation(spec, state, attestation)
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
def test_inconsistent_bitfields(spec, state):
def test_inconsistent_bits(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield) + b'\x00'
custody_bits = attestation.aggregation_bits[:]
custody_bits.append(False)
attestation.custody_bits = custody_bits
sign_attestation(spec, state, attestation)
@ -290,11 +375,11 @@ def test_inconsistent_bitfields(spec, state):
@with_phases(['phase0'])
@spec_state_test
def test_non_empty_custody_bitfield(spec, state):
def test_non_empty_custody_bits(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation.custody_bitfield = deepcopy(attestation.aggregation_bitfield)
attestation.custody_bits = attestation.aggregation_bits[:]
sign_attestation(spec, state, attestation)
@ -303,11 +388,12 @@ def test_non_empty_custody_bitfield(spec, state):
@with_all_phases
@spec_state_test
def test_empty_aggregation_bitfield(spec, state):
def test_empty_aggregation_bits(spec, state):
attestation = get_valid_attestation(spec, state)
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation.aggregation_bitfield = b'\x00' * len(attestation.aggregation_bitfield)
attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](
*([0b0] * len(attestation.aggregation_bits)))
sign_attestation(spec, state, attestation)

View File

@ -25,31 +25,56 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True)
yield 'post', None
return
slashed_index = attester_slashing.attestation_1.custody_bit_0_indices[0]
pre_slashed_balance = get_balance(state, slashed_index)
slashed_indices = (
attester_slashing.attestation_1.custody_bit_0_indices
+ attester_slashing.attestation_1.custody_bit_1_indices
)
proposer_index = spec.get_beacon_proposer_index(state)
pre_proposer_balance = get_balance(state, proposer_index)
pre_slashings = {slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices}
pre_withdrawalable_epochs = {
slashed_index: state.validators[slashed_index].withdrawable_epoch
for slashed_index in slashed_indices
}
total_proposer_rewards = sum(
balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT
for balance in pre_slashings.values()
)
# Process slashing
spec.process_attester_slashing(state, attester_slashing)
slashed_validator = state.validator_registry[slashed_index]
for slashed_index in slashed_indices:
pre_withdrawalable_epoch = pre_withdrawalable_epochs[slashed_index]
slashed_validator = state.validators[slashed_index]
# Check slashing
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
if pre_withdrawalable_epoch < spec.FAR_FUTURE_EPOCH:
expected_withdrawable_epoch = max(
pre_withdrawalable_epoch,
spec.get_current_epoch(state) + spec.EPOCHS_PER_SLASHINGS_VECTOR
)
assert slashed_validator.withdrawable_epoch == expected_withdrawable_epoch
else:
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
assert get_balance(state, slashed_index) < pre_slashings[slashed_index]
if slashed_index != proposer_index:
# lost whistleblower reward
assert get_balance(state, slashed_index) < pre_slashed_balance
if proposer_index not in slashed_indices:
# gained whistleblower reward
assert get_balance(state, proposer_index) > pre_proposer_balance
assert get_balance(state, proposer_index) == pre_proposer_balance + total_proposer_rewards
else:
# gained rewards for all slashings, which may include others. And only lost that of themselves.
# Netto at least 0, if more people where slashed, a balance increase.
assert get_balance(state, slashed_index) >= pre_slashed_balance
expected_balance = (
pre_proposer_balance
+ total_proposer_rewards
- pre_slashings[proposer_index] // spec.MIN_SLASHING_PENALTY_QUOTIENT
)
assert get_balance(state, proposer_index) == expected_balance
yield 'post', state
@ -68,18 +93,51 @@ def test_success_surround(spec, state):
next_epoch(spec, state)
apply_empty_block(spec, state)
state.current_justified_epoch += 1
state.current_justified_checkpoint.epoch += 1
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
attestation_1 = attester_slashing.attestation_1
attestation_2 = attester_slashing.attestation_2
# set attestion1 to surround attestation 2
attester_slashing.attestation_1.data.source_epoch = attester_slashing.attestation_2.data.source_epoch - 1
attester_slashing.attestation_1.data.target_epoch = attester_slashing.attestation_2.data.target_epoch + 1
attestation_1.data.source.epoch = attestation_2.data.source.epoch - 1
attestation_1.data.target.epoch = attestation_2.data.target.epoch + 1
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
yield from run_attester_slashing_processing(spec, state, attester_slashing)
@with_all_phases
@always_bls
@spec_state_test
def test_success_already_exited_recent(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
slashed_indices = (
attester_slashing.attestation_1.custody_bit_0_indices
+ attester_slashing.attestation_1.custody_bit_1_indices
)
for index in slashed_indices:
spec.initiate_validator_exit(state, index)
yield from run_attester_slashing_processing(spec, state, attester_slashing)
@with_all_phases
@always_bls
@spec_state_test
def test_success_already_exited_long_ago(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
slashed_indices = (
attester_slashing.attestation_1.custody_bit_0_indices
+ attester_slashing.attestation_1.custody_bit_1_indices
)
for index in slashed_indices:
spec.initiate_validator_exit(state, index)
state.validators[index].withdrawable_epoch = spec.get_current_epoch(state) + 2
yield from run_attester_slashing_processing(spec, state, attester_slashing)
@with_all_phases
@always_bls
@spec_state_test
@ -120,7 +178,7 @@ def test_same_data(spec, state):
def test_no_double_or_surround(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
attester_slashing.attestation_1.data.target_epoch += 1
attester_slashing.attestation_1.data.target.epoch += 1
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@ -135,19 +193,113 @@ def test_participants_already_slashed(spec, state):
attestation_1 = attester_slashing.attestation_1
validator_indices = attestation_1.custody_bit_0_indices + attestation_1.custody_bit_1_indices
for index in validator_indices:
state.validator_registry[index].slashed = True
state.validators[index].slashed = True
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@spec_state_test
def test_custody_bit_0_and_1(spec, state):
def test_custody_bit_0_and_1_intersect(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
attester_slashing.attestation_1.custody_bit_1_indices = (
attester_slashing.attestation_1.custody_bit_0_indices
attester_slashing.attestation_1.custody_bit_1_indices.append(
attester_slashing.attestation_1.custody_bit_0_indices[0]
)
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@always_bls
@with_all_phases
@spec_state_test
def test_att1_bad_extra_index(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
indices = attester_slashing.attestation_1.custody_bit_0_indices
options = list(set(range(len(state.validators))) - set(indices))
indices.append(options[len(options) // 2]) # add random index, not previously in attestation.
attester_slashing.attestation_1.custody_bit_0_indices = sorted(indices)
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
# see if the bad extra index is spotted, and slashing is aborted.
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@always_bls
@with_all_phases
@spec_state_test
def test_att1_bad_replaced_index(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
indices = attester_slashing.attestation_1.custody_bit_0_indices
options = list(set(range(len(state.validators))) - set(indices))
indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation.
attester_slashing.attestation_1.custody_bit_0_indices = sorted(indices)
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
# see if the bad replaced index is spotted, and slashing is aborted.
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@always_bls
@with_all_phases
@spec_state_test
def test_att2_bad_extra_index(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
indices = attester_slashing.attestation_2.custody_bit_0_indices
options = list(set(range(len(state.validators))) - set(indices))
indices.append(options[len(options) // 2]) # add random index, not previously in attestation.
attester_slashing.attestation_2.custody_bit_0_indices = sorted(indices)
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
# see if the bad extra index is spotted, and slashing is aborted.
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@always_bls
@with_all_phases
@spec_state_test
def test_att2_bad_replaced_index(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
indices = attester_slashing.attestation_2.custody_bit_0_indices
options = list(set(range(len(state.validators))) - set(indices))
indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation.
attester_slashing.attestation_2.custody_bit_0_indices = sorted(indices)
# Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not),
# see if the bad replaced index is spotted, and slashing is aborted.
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@spec_state_test
def test_unsorted_att_1_bit0(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
indices = attester_slashing.attestation_1.custody_bit_0_indices
assert len(indices) >= 3
indices[1], indices[2] = indices[2], indices[1] # unsort second and third index
sign_indexed_attestation(spec, state, attester_slashing.attestation_1)
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_all_phases
@spec_state_test
def test_unsorted_att_2_bit0(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)
indices = attester_slashing.attestation_2.custody_bit_0_indices
assert len(indices) >= 3
indices[1], indices[2] = indices[2], indices[1] # unsort second and third index
sign_indexed_attestation(spec, state, attester_slashing.attestation_2)
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
# note: unsorted indices for custody bit 0 are to be introduced in phase 1 testing.

View File

@ -78,7 +78,7 @@ def test_proposer_slashed(spec, state):
proposer_index = spec.get_beacon_proposer_index(stub_state)
# set proposer to slashed
state.validator_registry[proposer_index].slashed = True
state.validators[proposer_index].slashed = True
block = build_empty_block_for_next_slot(spec, state, signed=True)

View File

@ -16,7 +16,7 @@ def run_deposit_processing(spec, state, deposit, validator_index, valid=True, ef
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
pre_validator_count = len(state.validator_registry)
pre_validator_count = len(state.validators)
pre_balance = 0
if validator_index < pre_validator_count:
pre_balance = get_balance(state, validator_index)
@ -34,41 +34,71 @@ def run_deposit_processing(spec, state, deposit, validator_index, valid=True, ef
yield 'post', state
if not effective:
assert len(state.validator_registry) == pre_validator_count
assert len(state.validators) == pre_validator_count
assert len(state.balances) == pre_validator_count
if validator_index < pre_validator_count:
assert get_balance(state, validator_index) == pre_balance
else:
if validator_index < pre_validator_count:
# top-up
assert len(state.validator_registry) == pre_validator_count
assert len(state.validators) == pre_validator_count
assert len(state.balances) == pre_validator_count
else:
# new validator
assert len(state.validator_registry) == pre_validator_count + 1
assert len(state.validators) == pre_validator_count + 1
assert len(state.balances) == pre_validator_count + 1
assert get_balance(state, validator_index) == pre_balance + deposit.data.amount
assert state.deposit_index == state.latest_eth1_data.deposit_count
effective = min(spec.MAX_EFFECTIVE_BALANCE,
pre_balance + deposit.data.amount)
effective -= effective % spec.EFFECTIVE_BALANCE_INCREMENT
assert state.validators[validator_index].effective_balance == effective
assert state.eth1_deposit_index == state.eth1_data.deposit_count
@with_all_phases
@spec_state_test
def test_new_deposit(spec, state):
def test_new_deposit_under_max(spec, state):
# fresh deposit = next validator index = validator appended to registry
validator_index = len(state.validator_registry)
validator_index = len(state.validators)
# effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement.
amount = spec.MAX_EFFECTIVE_BALANCE - 1
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
yield from run_deposit_processing(spec, state, deposit, validator_index)
@with_all_phases
@spec_state_test
def test_new_deposit_max(spec, state):
# fresh deposit = next validator index = validator appended to registry
validator_index = len(state.validators)
# effective balance will be exactly the same as balance.
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
yield from run_deposit_processing(spec, state, deposit, validator_index)
@with_all_phases
@spec_state_test
def test_new_deposit_over_max(spec, state):
# fresh deposit = next validator index = validator appended to registry
validator_index = len(state.validators)
# just 1 over the limit, effective balance should be set MAX_EFFECTIVE_BALANCE during processing
amount = spec.MAX_EFFECTIVE_BALANCE + 1
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
yield from run_deposit_processing(spec, state, deposit, validator_index)
@with_all_phases
@always_bls
@spec_state_test
def test_invalid_sig_new_deposit(spec, state):
# fresh deposit = next validator index = validator appended to registry
validator_index = len(state.validator_registry)
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, validator_index, amount)
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=False)
@ -101,7 +131,7 @@ def test_invalid_sig_top_up(spec, state):
def test_invalid_withdrawal_credentials_top_up(spec, state):
validator_index = 0
amount = spec.MAX_EFFECTIVE_BALANCE // 4
withdrawal_credentials = spec.int_to_bytes(spec.BLS_WITHDRAWAL_PREFIX, length=1) + spec.hash(b"junk")[1:]
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(b"junk")[1:]
deposit = prepare_state_and_deposit(
spec,
state,
@ -114,25 +144,10 @@ def test_invalid_withdrawal_credentials_top_up(spec, state):
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True)
@with_all_phases
@spec_state_test
def test_wrong_index(spec, state):
validator_index = len(state.validator_registry)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, validator_index, amount)
# mess up deposit_index
deposit.index = state.deposit_index + 1
sign_deposit_data(spec, state, deposit.data, privkeys[validator_index])
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False)
@with_all_phases
@spec_state_test
def test_wrong_deposit_for_deposit_count(spec, state):
deposit_data_leaves = [spec.ZERO_HASH] * len(state.validator_registry)
deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))]
# build root for deposit_1
index_1 = len(deposit_data_leaves)
@ -166,25 +181,22 @@ def test_wrong_deposit_for_deposit_count(spec, state):
)
# state has root for deposit_2 but is at deposit_count for deposit_1
state.latest_eth1_data.deposit_root = root_2
state.latest_eth1_data.deposit_count = deposit_count_1
state.eth1_data.deposit_root = root_2
state.eth1_data.deposit_count = deposit_count_1
yield from run_deposit_processing(spec, state, deposit_2, index_2, valid=False)
# TODO: test invalid signature
@with_all_phases
@spec_state_test
def test_bad_merkle_proof(spec, state):
validator_index = len(state.validator_registry)
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, validator_index, amount)
# mess up merkle branch
deposit.proof[-1] = spec.ZERO_HASH
deposit.proof[5] = spec.Hash()
sign_deposit_data(spec, state, deposit.data, privkeys[validator_index])
sign_deposit_data(spec, deposit.data, privkeys[validator_index], state=state)
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False)

View File

@ -28,7 +28,7 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True)
yield 'post', state
# check if slashed
slashed_validator = state.validator_registry[proposer_slashing.proposer_index]
slashed_validator = state.validators[proposer_slashing.proposer_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
@ -77,7 +77,7 @@ def test_invalid_sig_1_and_2(spec, state):
def test_invalid_proposer_index(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# Index just too high (by 1)
proposer_slashing.proposer_index = len(state.validator_registry)
proposer_slashing.proposer_index = len(state.validators)
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -111,7 +111,7 @@ def test_proposer_is_not_activated(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set proposer to be not active yet
state.validator_registry[proposer_slashing.proposer_index].activation_epoch = spec.get_current_epoch(state) + 1
state.validators[proposer_slashing.proposer_index].activation_epoch = spec.get_current_epoch(state) + 1
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -122,7 +122,7 @@ def test_proposer_is_slashed(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# set proposer to slashed
state.validator_registry[proposer_slashing.proposer_index].slashed = True
state.validators[proposer_slashing.proposer_index].slashed = True
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@ -137,6 +137,6 @@ def test_proposer_is_withdrawn(spec, state):
# set proposer withdrawable_epoch in past
current_epoch = spec.get_current_epoch(state)
proposer_index = proposer_slashing.proposer_index
state.validator_registry[proposer_index].withdrawable_epoch = current_epoch - 1
state.validators[proposer_index].withdrawable_epoch = current_epoch - 1
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

View File

@ -1,7 +1,7 @@
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
from eth2spec.test.helpers.state import next_epoch
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.test.helpers.transfers import get_valid_transfer
from eth2spec.test.helpers.transfers import get_valid_transfer, sign_transfer
def run_transfer_processing(spec, state, transfer, valid=True):
@ -13,11 +13,6 @@ def run_transfer_processing(spec, state, transfer, valid=True):
If ``valid == False``, run expecting ``AssertionError``
"""
proposer_index = spec.get_beacon_proposer_index(state)
pre_transfer_sender_balance = state.balances[transfer.sender]
pre_transfer_recipient_balance = state.balances[transfer.recipient]
pre_transfer_proposer_balance = state.balances[proposer_index]
yield 'pre', state
yield 'transfer', transfer
@ -26,6 +21,11 @@ def run_transfer_processing(spec, state, transfer, valid=True):
yield 'post', None
return
proposer_index = spec.get_beacon_proposer_index(state)
pre_transfer_sender_balance = state.balances[transfer.sender]
pre_transfer_recipient_balance = state.balances[transfer.recipient]
pre_transfer_proposer_balance = state.balances[proposer_index]
spec.process_transfer(state, transfer)
yield 'post', state
@ -41,7 +41,7 @@ def run_transfer_processing(spec, state, transfer, valid=True):
def test_success_non_activated(spec, state):
transfer = get_valid_transfer(spec, state, signed=True)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer)
@ -55,7 +55,7 @@ def test_success_withdrawable(spec, state):
transfer = get_valid_transfer(spec, state, signed=True)
# withdrawable_epoch in past so can transfer
state.validator_registry[transfer.sender].withdrawable_epoch = spec.get_current_epoch(state) - 1
state.validators[transfer.sender].withdrawable_epoch = spec.get_current_epoch(state) - 1
yield from run_transfer_processing(spec, state, transfer)
@ -86,7 +86,7 @@ def test_success_active_above_max_effective_fee(spec, state):
def test_invalid_signature(spec, state):
transfer = get_valid_transfer(spec, state)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@ -107,33 +107,195 @@ def test_active_but_transfer_past_effective_balance(spec, state):
def test_incorrect_slot(spec, state):
transfer = get_valid_transfer(spec, state, slot=state.slot + 1, signed=True)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_transfer_clean(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=spec.MIN_DEPOSIT_AMOUNT, fee=0, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer)
@with_all_phases
@spec_state_test
def test_transfer_clean_split_to_fee(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=spec.MIN_DEPOSIT_AMOUNT // 2, fee=spec.MIN_DEPOSIT_AMOUNT // 2, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_fee(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=0, fee=1, signed=True)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance(spec, state):
def test_insufficient_balance_for_fee_result_full(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=0, fee=state.balances[sender_index] + 1, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_amount_result_dust(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=1, fee=0, signed=True)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_amount_result_full(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=state.balances[sender_index] + 1, fee=0, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_combined_result_dust(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee without dust, and amount without dust, but not both.
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT + 1
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=1, fee=1, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_combined_result_full(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee fully without dust left, and amount fully without dust left, but not both.
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT * 2 + 1
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=spec.MIN_DEPOSIT_AMOUNT + 1,
fee=spec.MIN_DEPOSIT_AMOUNT + 1, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_combined_big_amount(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee fully without dust left, and amount fully without dust left, but not both.
# Try to create a dust balance (off by 1) with combination of fee and amount.
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT * 2 + 1
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=spec.MIN_DEPOSIT_AMOUNT + 1, fee=1, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_for_combined_big_fee(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee fully without dust left, and amount fully without dust left, but not both.
# Try to create a dust balance (off by 1) with combination of fee and amount.
state.balances[sender_index] = spec.MIN_DEPOSIT_AMOUNT * 2 + 1
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=1, fee=spec.MIN_DEPOSIT_AMOUNT + 1, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_off_by_1_fee(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee fully without dust left, and amount fully without dust left, but not both.
# Try to print money by using the full balance as amount, plus 1 for fee.
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=state.balances[sender_index], fee=1, signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_off_by_1_amount(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee fully without dust left, and amount fully without dust left, but not both.
# Try to print money by using the full balance as fee, plus 1 for amount.
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=1,
fee=state.balances[sender_index], signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_insufficient_balance_duplicate_as_fee_and_amount(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
# Enough to pay fee fully without dust left, and amount fully without dust left, but not both.
# Try to print money by using the full balance, twice.
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
amount=state.balances[sender_index],
fee=state.balances[sender_index], signed=True)
# un-activate so validator can transfer
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@ -153,7 +315,7 @@ def test_no_dust_sender(spec, state):
)
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@ -167,7 +329,29 @@ def test_no_dust_recipient(spec, state):
state.balances[transfer.recipient] = 0
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_non_existent_sender(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
transfer = get_valid_transfer(spec, state, sender_index=sender_index, amount=1, fee=0)
transfer.sender = len(state.validators)
sign_transfer(spec, state, transfer, 42) # mostly valid signature, but sender won't exist, use bogus key.
yield from run_transfer_processing(spec, state, transfer, False)
@with_all_phases
@spec_state_test
def test_non_existent_recipient(spec, state):
sender_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + 1
transfer = get_valid_transfer(spec, state, sender_index=sender_index,
recipient_index=len(state.validators), amount=1, fee=0, signed=True)
yield from run_transfer_processing(spec, state, transfer, False)
@ -176,9 +360,9 @@ def test_no_dust_recipient(spec, state):
@spec_state_test
def test_invalid_pubkey(spec, state):
transfer = get_valid_transfer(spec, state, signed=True)
state.validator_registry[transfer.sender].withdrawal_credentials = spec.ZERO_HASH
state.validators[transfer.sender].withdrawal_credentials = spec.Hash()
# un-activate so validator can transfer
state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
yield from run_transfer_processing(spec, state, transfer, False)

View File

@ -21,14 +21,14 @@ def run_voluntary_exit_processing(spec, state, voluntary_exit, valid=True):
yield 'post', None
return
pre_exit_epoch = state.validator_registry[validator_index].exit_epoch
pre_exit_epoch = state.validators[validator_index].exit_epoch
spec.process_voluntary_exit(state, voluntary_exit)
yield 'post', state
assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@with_all_phases
@ -39,7 +39,7 @@ def test_success(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
voluntary_exit = build_voluntary_exit(spec, state, current_epoch, validator_index, privkey, signed=True)
@ -55,7 +55,7 @@ def test_invalid_signature(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
voluntary_exit = build_voluntary_exit(spec, state, current_epoch, validator_index, privkey)
@ -71,12 +71,12 @@ def test_success_exit_queue(spec, state):
current_epoch = spec.get_current_epoch(state)
# exit `MAX_EXITS_PER_EPOCH`
initial_indices = spec.get_active_validator_indices(state, current_epoch)[:spec.get_churn_limit(state)]
initial_indices = spec.get_active_validator_indices(state, current_epoch)[:spec.get_validator_churn_limit(state)]
# Prepare a bunch of exits, based on the current state
exit_queue = []
for index in initial_indices:
privkey = pubkey_to_privkey[state.validator_registry[index].pubkey]
privkey = pubkey_to_privkey[state.validators[index].pubkey]
exit_queue.append(build_voluntary_exit(
spec,
state,
@ -94,7 +94,7 @@ def test_success_exit_queue(spec, state):
# exit an additional validator
validator_index = spec.get_active_validator_indices(state, current_epoch)[-1]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
voluntary_exit = build_voluntary_exit(
spec,
state,
@ -109,8 +109,8 @@ def test_success_exit_queue(spec, state):
yield from run_voluntary_exit_processing(spec, state, voluntary_exit)
assert (
state.validator_registry[validator_index].exit_epoch ==
state.validator_registry[initial_indices[0]].exit_epoch + 1
state.validators[validator_index].exit_epoch ==
state.validators[initial_indices[0]].exit_epoch + 1
)
@ -122,7 +122,7 @@ def test_validator_exit_in_future(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
voluntary_exit = build_voluntary_exit(
spec,
@ -146,7 +146,7 @@ def test_validator_invalid_validator_index(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
voluntary_exit = build_voluntary_exit(
spec,
@ -156,7 +156,7 @@ def test_validator_invalid_validator_index(spec, state):
privkey,
signed=False,
)
voluntary_exit.validator_index = len(state.validator_registry)
voluntary_exit.validator_index = len(state.validators)
sign_voluntary_exit(spec, state, voluntary_exit, privkey)
yield from run_voluntary_exit_processing(spec, state, voluntary_exit, False)
@ -167,9 +167,9 @@ def test_validator_invalid_validator_index(spec, state):
def test_validator_not_active(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
# build and test voluntary exit
voluntary_exit = build_voluntary_exit(
@ -192,10 +192,10 @@ def test_validator_already_exited(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
# but validator already has exited
state.validator_registry[validator_index].exit_epoch = current_epoch + 2
state.validators[validator_index].exit_epoch = current_epoch + 2
voluntary_exit = build_voluntary_exit(
spec,
@ -214,7 +214,7 @@ def test_validator_already_exited(spec, state):
def test_validator_not_active_long_enough(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[0]
privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey]
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
voluntary_exit = build_voluntary_exit(
spec,
@ -226,7 +226,7 @@ def test_validator_not_active_long_enough(spec, state):
)
assert (
current_epoch - state.validator_registry[validator_index].activation_epoch <
current_epoch - state.validators[validator_index].activation_epoch <
spec.PERSISTENT_COMMITTEE_PERIOD
)

View File

@ -0,0 +1,45 @@
process_calls = [
'process_justification_and_finalization',
'process_crosslinks',
'process_rewards_and_penalties',
'process_registry_updates',
'process_reveal_deadlines',
'process_challenge_deadlines',
'process_slashings',
'process_final_updates',
'after_process_final_updates',
]
def run_epoch_processing_to(spec, state, process_name: str):
"""
Processes to the next epoch transition, up to, but not including, the sub-transition named ``process_name``
"""
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH)
# transition state to slot before epoch state transition
spec.process_slots(state, slot - 1)
# start transitioning, do one slot update before the epoch itself.
spec.process_slot(state)
# process components of epoch transition before final-updates
for name in process_calls:
if name == process_name:
break
# only run when present. Later phases introduce more to the epoch-processing.
if hasattr(spec, name):
getattr(spec, name)(state)
def run_epoch_processing_with(spec, state, process_name: str):
"""
Processes to the next epoch transition, up to and including the sub-transition named ``process_name``
- pre-state ('pre'), state before calling ``process_name``
- post-state ('post'), state after calling ``process_name``
"""
run_epoch_processing_to(spec, state, process_name)
yield 'pre', state
getattr(spec, process_name)(state)
yield 'post', state

View File

@ -5,36 +5,18 @@ from eth2spec.test.helpers.state import (
next_epoch,
next_slot
)
from eth2spec.test.helpers.block import apply_empty_block, sign_block
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.test.helpers.attestations import (
add_attestation_to_state,
build_empty_block_for_next_slot,
fill_aggregate_attestation,
get_valid_attestation,
sign_attestation,
)
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
def run_process_crosslinks(spec, state, valid=True):
"""
Run ``process_crosslinks``, yielding:
- pre-state ('pre')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# transition state to slot before state transition
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
block = build_empty_block_for_next_slot(spec, state)
block.slot = slot
sign_block(spec, state, block)
spec.state_transition(state, block)
# cache state before epoch transition
spec.process_slot(state)
yield 'pre', state
spec.process_crosslinks(state)
yield 'post', state
def run_process_crosslinks(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_crosslinks')
@with_all_phases
@ -92,7 +74,7 @@ def test_single_crosslink_update_from_previous_epoch(spec, state):
# ensure rewarded
for index in spec.get_crosslink_committee(
state,
attestation.data.target_epoch,
attestation.data.target.epoch,
attestation.data.crosslink.shard):
assert crosslink_deltas[0][index] > 0
assert crosslink_deltas[1][index] == 0
@ -101,7 +83,7 @@ def test_single_crosslink_update_from_previous_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_double_late_crosslink(spec, state):
if spec.get_epoch_committee_count(state, spec.get_current_epoch(state)) < spec.SHARD_COUNT:
if spec.get_committee_count(state, spec.get_current_epoch(state)) < spec.SHARD_COUNT:
print("warning: ignoring test, test-assumptions are incompatible with configuration")
return
@ -144,7 +126,7 @@ def test_double_late_crosslink(spec, state):
# ensure no reward, only penalties for the failed crosslink
for index in spec.get_crosslink_committee(
state,
attestation_2.data.target_epoch,
attestation_2.data.target.epoch,
attestation_2.data.crosslink.shard):
assert crosslink_deltas[0][index] == 0
assert crosslink_deltas[1][index] > 0

View File

@ -0,0 +1,91 @@
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
run_epoch_processing_with, run_epoch_processing_to
)
def run_process_final_updates(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_final_updates')
@with_all_phases
@spec_state_test
def test_eth1_vote_no_reset(spec, state):
assert spec.SLOTS_PER_ETH1_VOTING_PERIOD > spec.SLOTS_PER_EPOCH
# skip ahead to the end of the epoch
state.slot = spec.SLOTS_PER_EPOCH - 1
for i in range(state.slot + 1): # add a vote for each skipped slot.
state.eth1_data_votes.append(
spec.Eth1Data(deposit_root=b'\xaa' * 32,
deposit_count=state.eth1_deposit_index,
block_hash=b'\xbb' * 32))
yield from run_process_final_updates(spec, state)
assert len(state.eth1_data_votes) == spec.SLOTS_PER_EPOCH
@with_all_phases
@spec_state_test
def test_eth1_vote_reset(spec, state):
# skip ahead to the end of the voting period
state.slot = spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1
for i in range(state.slot + 1): # add a vote for each skipped slot.
state.eth1_data_votes.append(
spec.Eth1Data(deposit_root=b'\xaa' * 32,
deposit_count=state.eth1_deposit_index,
block_hash=b'\xbb' * 32))
yield from run_process_final_updates(spec, state)
assert len(state.eth1_data_votes) == 0
@with_all_phases
@spec_state_test
def test_effective_balance_hysteresis(spec, state):
# Prepare state up to the final-updates.
# Then overwrite the balances, we only want to focus to be on the hysteresis based changes.
run_epoch_processing_to(spec, state, 'process_final_updates')
# Set some edge cases for balances
max = spec.MAX_EFFECTIVE_BALANCE
min = spec.EJECTION_BALANCE
inc = spec.EFFECTIVE_BALANCE_INCREMENT
half_inc = inc // 2
cases = [
(max, max, max, "as-is"),
(max, max - 1, max - inc, "round down, step lower"),
(max, max + 1, max, "round down"),
(max, max - inc, max - inc, "exactly 1 step lower"),
(max, max - inc - 1, max - (2 * inc), "just 1 over 1 step lower"),
(max, max - inc + 1, max - inc, "close to 1 step lower"),
(min, min + (half_inc * 3), min, "bigger balance, but not high enough"),
(min, min + (half_inc * 3) + 1, min + inc, "bigger balance, high enough, but small step"),
(min, min + (half_inc * 4) - 1, min + inc, "bigger balance, high enough, close to double step"),
(min, min + (half_inc * 4), min + (2 * inc), "exact two step balance increment"),
(min, min + (half_inc * 4) + 1, min + (2 * inc), "over two steps, round down"),
]
current_epoch = spec.get_current_epoch(state)
for i, (pre_eff, bal, _, _) in enumerate(cases):
assert spec.is_active_validator(state.validators[i], current_epoch)
state.validators[i].effective_balance = pre_eff
state.balances[i] = bal
yield 'pre', state
spec.process_final_updates(state)
yield 'post', state
for i, (_, _, post_eff, name) in enumerate(cases):
assert state.validators[i].effective_balance == post_eff, name
@with_all_phases
@spec_state_test
def test_historical_root_accumulator(spec, state):
# skip ahead to near the end of the historical roots period (excl block before epoch processing)
state.slot = spec.SLOTS_PER_HISTORICAL_ROOT - 1
history_len = len(state.historical_roots)
yield from run_process_final_updates(spec, state)
assert len(state.historical_roots) == history_len + 1

View File

@ -0,0 +1,280 @@
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
run_epoch_processing_with
)
def run_process_just_and_fin(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_justification_and_finalization')
def get_shards_for_slot(spec, state, slot):
epoch = spec.compute_epoch_of_slot(slot)
epoch_start_shard = spec.get_start_shard(state, epoch)
committees_per_slot = spec.get_committee_count(state, epoch) // spec.SLOTS_PER_EPOCH
shard = (epoch_start_shard + committees_per_slot * (slot % spec.SLOTS_PER_EPOCH)) % spec.SHARD_COUNT
return [shard + i for i in range(committees_per_slot)]
def add_mock_attestations(spec, state, epoch, source, target, sufficient_support=False):
# we must be at the end of the epoch
assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0
previous_epoch = spec.get_previous_epoch(state)
current_epoch = spec.get_current_epoch(state)
if current_epoch == epoch:
attestations = state.current_epoch_attestations
elif previous_epoch == epoch:
attestations = state.previous_epoch_attestations
else:
raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}")
total_balance = spec.get_total_active_balance(state)
remaining_balance = total_balance * 2 // 3
start_slot = spec.compute_start_slot_of_epoch(epoch)
for slot in range(start_slot, start_slot + spec.SLOTS_PER_EPOCH):
for shard in get_shards_for_slot(spec, state, slot):
# Check if we already have had sufficient balance. (and undone if we don't want it).
# If so, do not create more attestations. (we do not have empty pending attestations normally anyway)
if remaining_balance < 0:
return
committee = spec.get_crosslink_committee(state, spec.compute_epoch_of_slot(slot), shard)
# Create a bitfield filled with the given count per attestation,
# exactly on the right-most part of the committee field.
aggregation_bits = [0] * len(committee)
for v in range(len(committee) * 2 // 3 + 1):
if remaining_balance > 0:
remaining_balance -= state.validators[v].effective_balance
aggregation_bits[v] = 1
else:
break
# remove just one attester to make the marginal support insufficient
if not sufficient_support:
aggregation_bits[aggregation_bits.index(1)] = 0
attestations.append(spec.PendingAttestation(
aggregation_bits=aggregation_bits,
data=spec.AttestationData(
beacon_block_root=b'\xff' * 32, # irrelevant to testing
source=source,
target=target,
crosslink=spec.Crosslink(shard=shard)
),
inclusion_delay=1,
))
def get_checkpoints(spec, epoch):
c1 = None if epoch < 1 else spec.Checkpoint(epoch=epoch - 1, root=b'\xaa' * 32)
c2 = None if epoch < 2 else spec.Checkpoint(epoch=epoch - 2, root=b'\xbb' * 32)
c3 = None if epoch < 3 else spec.Checkpoint(epoch=epoch - 3, root=b'\xcc' * 32)
c4 = None if epoch < 4 else spec.Checkpoint(epoch=epoch - 4, root=b'\xdd' * 32)
c5 = None if epoch < 5 else spec.Checkpoint(epoch=epoch - 5, root=b'\xee' * 32)
return c1, c2, c3, c4, c5
def put_checkpoints_in_block_roots(spec, state, checkpoints):
for c in checkpoints:
state.block_roots[spec.compute_start_slot_of_epoch(c.epoch) % spec.SLOTS_PER_HISTORICAL_ROOT] = c.root
def finalize_on_234(spec, state, epoch, sufficient_support):
assert epoch > 4
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
# 43210 -- epochs ago
# 3210x -- justification bitfield indices
# 11*0. -- justification bitfield contents, . = this epoch, * is being justified now
# checkpoints for the epochs ago:
c1, c2, c3, c4, _ = get_checkpoints(spec, epoch)
put_checkpoints_in_block_roots(spec, state, [c1, c2, c3, c4])
old_finalized = state.finalized_checkpoint
state.previous_justified_checkpoint = c4
state.current_justified_checkpoint = c3
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
state.justification_bits[1:3] = [1, 1] # mock 3rd and 4th latest epochs as justified (indices are pre-shift)
# mock the 2nd latest epoch as justifiable, with 4th as source
add_mock_attestations(spec, state,
epoch=epoch - 2,
source=c4,
target=c2,
sufficient_support=sufficient_support)
# process!
yield from run_process_just_and_fin(spec, state)
assert state.previous_justified_checkpoint == c3 # changed to old current
if sufficient_support:
assert state.current_justified_checkpoint == c2 # changed to 2nd latest
assert state.finalized_checkpoint == c4 # finalized old previous justified epoch
else:
assert state.current_justified_checkpoint == c3 # still old current
assert state.finalized_checkpoint == old_finalized # no new finalized
def finalize_on_23(spec, state, epoch, sufficient_support):
assert epoch > 3
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
# 43210 -- epochs ago
# 210xx -- justification bitfield indices (pre shift)
# 3210x -- justification bitfield indices (post shift)
# 01*0. -- justification bitfield contents, . = this epoch, * is being justified now
# checkpoints for the epochs ago:
c1, c2, c3, _, _ = get_checkpoints(spec, epoch)
put_checkpoints_in_block_roots(spec, state, [c1, c2, c3])
old_finalized = state.finalized_checkpoint
state.previous_justified_checkpoint = c3
state.current_justified_checkpoint = c3
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
state.justification_bits[1] = 1 # mock 3rd latest epoch as justified (index is pre-shift)
# mock the 2nd latest epoch as justifiable, with 3rd as source
add_mock_attestations(spec, state,
epoch=epoch - 2,
source=c3,
target=c2,
sufficient_support=sufficient_support)
# process!
yield from run_process_just_and_fin(spec, state)
assert state.previous_justified_checkpoint == c3 # changed to old current
if sufficient_support:
assert state.current_justified_checkpoint == c2 # changed to 2nd latest
assert state.finalized_checkpoint == c3 # finalized old previous justified epoch
else:
assert state.current_justified_checkpoint == c3 # still old current
assert state.finalized_checkpoint == old_finalized # no new finalized
def finalize_on_123(spec, state, epoch, sufficient_support):
assert epoch > 5
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
# 43210 -- epochs ago
# 210xx -- justification bitfield indices (pre shift)
# 3210x -- justification bitfield indices (post shift)
# 011*. -- justification bitfield contents, . = this epoch, * is being justified now
# checkpoints for the epochs ago:
c1, c2, c3, c4, c5 = get_checkpoints(spec, epoch)
put_checkpoints_in_block_roots(spec, state, [c1, c2, c3, c4, c5])
old_finalized = state.finalized_checkpoint
state.previous_justified_checkpoint = c5
state.current_justified_checkpoint = c3
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
state.justification_bits[1] = 1 # mock 3rd latest epochs as justified (index is pre-shift)
# mock the 2nd latest epoch as justifiable, with 5th as source
add_mock_attestations(spec, state,
epoch=epoch - 2,
source=c5,
target=c2,
sufficient_support=sufficient_support)
# mock the 1st latest epoch as justifiable, with 3rd as source
add_mock_attestations(spec, state,
epoch=epoch - 1,
source=c3,
target=c1,
sufficient_support=sufficient_support)
# process!
yield from run_process_just_and_fin(spec, state)
assert state.previous_justified_checkpoint == c3 # changed to old current
if sufficient_support:
assert state.current_justified_checkpoint == c1 # changed to 1st latest
assert state.finalized_checkpoint == c3 # finalized old current
else:
assert state.current_justified_checkpoint == c3 # still old current
assert state.finalized_checkpoint == old_finalized # no new finalized
def finalize_on_12(spec, state, epoch, sufficient_support):
assert epoch > 2
state.slot = (spec.SLOTS_PER_EPOCH * epoch) - 1 # skip ahead to just before epoch
# 43210 -- epochs ago
# 210xx -- justification bitfield indices (pre shift)
# 3210x -- justification bitfield indices (post shift)
# 001*. -- justification bitfield contents, . = this epoch, * is being justified now
# checkpoints for the epochs ago:
c1, c2, _, _, _ = get_checkpoints(spec, epoch)
put_checkpoints_in_block_roots(spec, state, [c1, c2])
old_finalized = state.finalized_checkpoint
state.previous_justified_checkpoint = c2
state.current_justified_checkpoint = c2
state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]()
state.justification_bits[0] = 1 # mock 2nd latest epoch as justified (this is pre-shift)
# mock the 1st latest epoch as justifiable, with 2nd as source
add_mock_attestations(spec, state,
epoch=epoch - 1,
source=c2,
target=c1,
sufficient_support=sufficient_support)
# process!
yield from run_process_just_and_fin(spec, state)
assert state.previous_justified_checkpoint == c2 # changed to old current
if sufficient_support:
assert state.current_justified_checkpoint == c1 # changed to 1st latest
assert state.finalized_checkpoint == c2 # finalized previous justified epoch
else:
assert state.current_justified_checkpoint == c2 # still old current
assert state.finalized_checkpoint == old_finalized # no new finalized
@with_all_phases
@spec_state_test
def test_234_ok_support(spec, state):
yield from finalize_on_234(spec, state, 5, True)
@with_all_phases
@spec_state_test
def test_234_poor_support(spec, state):
yield from finalize_on_234(spec, state, 5, False)
@with_all_phases
@spec_state_test
def test_23_ok_support(spec, state):
yield from finalize_on_23(spec, state, 4, True)
@with_all_phases
@spec_state_test
def test_23_poor_support(spec, state):
yield from finalize_on_23(spec, state, 4, False)
@with_all_phases
@spec_state_test
def test_123_ok_support(spec, state):
yield from finalize_on_123(spec, state, 6, True)
@with_all_phases
@spec_state_test
def test_123_poor_support(spec, state):
yield from finalize_on_123(spec, state, 6, False)
@with_all_phases
@spec_state_test
def test_12_ok_support(spec, state):
yield from finalize_on_12(spec, state, 3, True)
@with_all_phases
@spec_state_test
def test_12_poor_support(spec, state):
yield from finalize_on_12(spec, state, 3, False)

View File

@ -1,78 +1,85 @@
from eth2spec.phase0.spec import state_transition
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
from eth2spec.test.helpers.state import next_epoch
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
def run_process_registry_updates(spec, state, valid=True):
"""
Run ``process_crosslinks``, yielding:
- pre-state ('pre')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# transition state to slot before state transition
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
block = build_empty_block_for_next_slot(spec, state)
block.slot = slot
sign_block(spec, state, block)
state_transition(state, block)
def run_process_registry_updates(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_registry_updates')
# cache state before epoch transition
spec.process_slot(state)
# process components of epoch transition before registry update
spec.process_justification_and_finalization(state)
spec.process_crosslinks(state)
spec.process_rewards_and_penalties(state)
yield 'pre', state
spec.process_registry_updates(state)
yield 'post', state
def mock_deposit(spec, state, index):
assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
state.validators[index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validators[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE
assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
@with_all_phases
@spec_state_test
def test_activation(spec, state):
index = 0
assert spec.is_active_validator(state.validator_registry[index], spec.get_current_epoch(state))
# Mock a new deposit
state.validator_registry[index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
state.validator_registry[index].activation_epoch = spec.FAR_FUTURE_EPOCH
state.validator_registry[index].effective_balance = spec.MAX_EFFECTIVE_BALANCE
assert not spec.is_active_validator(state.validator_registry[index], spec.get_current_epoch(state))
mock_deposit(spec, state, index)
for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
next_epoch(spec, state)
yield from run_process_registry_updates(spec, state)
assert state.validator_registry[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert state.validator_registry[index].activation_epoch != spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(
state.validator_registry[index],
spec.get_current_epoch(state),
)
assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH
assert state.validators[index].activation_epoch != spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
@with_all_phases
@spec_state_test
def test_activation_queue_sorting(spec, state):
mock_activations = 10
epoch = spec.get_current_epoch(state)
for i in range(mock_activations):
mock_deposit(spec, state, i)
state.validators[i].activation_eligibility_epoch = epoch + 1
# give the last priority over the others
state.validators[mock_activations - 1].activation_eligibility_epoch = epoch
# make sure we are hitting the churn
churn_limit = spec.get_validator_churn_limit(state)
assert mock_activations > churn_limit
yield from run_process_registry_updates(spec, state)
# the first got in as second
assert state.validators[0].activation_epoch != spec.FAR_FUTURE_EPOCH
# the prioritized got in as first
assert state.validators[mock_activations - 1].activation_epoch != spec.FAR_FUTURE_EPOCH
# the second last is at the end of the queue, and did not make the churn,
# hence is not assigned an activation_epoch yet.
assert state.validators[mock_activations - 2].activation_epoch == spec.FAR_FUTURE_EPOCH
# the one at churn_limit - 1 did not make it, it was out-prioritized
assert state.validators[churn_limit - 1].activation_epoch == spec.FAR_FUTURE_EPOCH
# but the the one in front of the above did
assert state.validators[churn_limit - 2].activation_epoch != spec.FAR_FUTURE_EPOCH
@with_all_phases
@spec_state_test
def test_ejection(spec, state):
index = 0
assert spec.is_active_validator(state.validator_registry[index], spec.get_current_epoch(state))
assert state.validator_registry[index].exit_epoch == spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state))
assert state.validators[index].exit_epoch == spec.FAR_FUTURE_EPOCH
# Mock an ejection
state.validator_registry[index].effective_balance = spec.EJECTION_BALANCE
state.validators[index].effective_balance = spec.EJECTION_BALANCE
for _ in range(spec.ACTIVATION_EXIT_DELAY + 1):
next_epoch(spec, state)
yield from run_process_registry_updates(spec, state)
assert state.validator_registry[index].exit_epoch != spec.FAR_FUTURE_EPOCH
assert state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH
assert not spec.is_active_validator(
state.validator_registry[index],
state.validators[index],
spec.get_current_epoch(state),
)

View File

@ -0,0 +1,135 @@
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import (
run_epoch_processing_with, run_epoch_processing_to
)
def run_process_slashings(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_slashings')
def slash_validators(spec, state, indices, out_epochs):
total_slashed_balance = 0
for i, out_epoch in zip(indices, out_epochs):
v = state.validators[i]
v.slashed = True
spec.initiate_validator_exit(state, i)
v.withdrawable_epoch = out_epoch
total_slashed_balance += v.effective_balance
state.slashings[
spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR
] = total_slashed_balance
@with_all_phases
@spec_state_test
def test_max_penalties(spec, state):
slashed_count = (len(state.validators) // 3) + 1
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
slashed_indices = list(range(slashed_count))
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
total_balance = spec.get_total_active_balance(state)
total_penalties = sum(state.slashings)
assert total_balance // 3 <= total_penalties
yield from run_process_slashings(spec, state)
for i in slashed_indices:
assert state.balances[i] == 0
@with_all_phases
@spec_state_test
def test_small_penalty(spec, state):
# Just the bare minimum for this one validator
state.balances[0] = state.validators[0].effective_balance = spec.EJECTION_BALANCE
# All the other validators get the maximum.
for i in range(1, len(state.validators)):
state.validators[i].effective_balance = state.balances[i] = spec.MAX_EFFECTIVE_BALANCE
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
slash_validators(spec, state, [0], [out_epoch])
total_balance = spec.get_total_active_balance(state)
total_penalties = sum(state.slashings)
assert total_balance // 3 > total_penalties
run_epoch_processing_to(spec, state, 'process_slashings')
pre_slash_balances = list(state.balances)
yield 'pre', state
spec.process_slashings(state)
yield 'post', state
expected_penalty = (
state.validators[0].effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT
* (3 * total_penalties)
// total_balance
* spec.EFFECTIVE_BALANCE_INCREMENT
)
assert state.balances[0] == pre_slash_balances[0] - expected_penalty
@with_all_phases
@spec_state_test
def test_scaled_penalties(spec, state):
# skip to next epoch
state.slot = spec.SLOTS_PER_EPOCH
# Also mock some previous slashings, so that we test to have the delta in the penalties computation.
base = spec.EJECTION_BALANCE
incr = spec.EFFECTIVE_BALANCE_INCREMENT
# Just add some random slashings. non-zero slashings are at least the minimal effective balance.
state.slashings[0] = base + (incr * 12)
state.slashings[4] = base + (incr * 3)
state.slashings[5] = base + (incr * 6)
state.slashings[spec.EPOCHS_PER_SLASHINGS_VECTOR - 1] = base + (incr * 7)
slashed_count = len(state.validators) // 4
assert slashed_count > 10
# make the balances non-uniform.
# Otherwise it would just be a simple 3/4 balance slashing. Test the per-validator scaled penalties.
diff = spec.MAX_EFFECTIVE_BALANCE - base
increments = diff // incr
for i in range(10):
state.validators[i].effective_balance = base + (incr * (i % increments))
assert state.validators[i].effective_balance <= spec.MAX_EFFECTIVE_BALANCE
# add/remove some, see if balances different than the effective balances are picked up
state.balances[i] = state.validators[i].effective_balance + i - 5
total_balance = spec.get_total_active_balance(state)
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
slashed_indices = list(range(slashed_count))
# Process up to the sub-transition, then Hi-jack and get the balances.
# We just want to test the slashings.
# But we are not interested in the other balance changes during the same epoch transition.
run_epoch_processing_to(spec, state, 'process_slashings')
pre_slash_balances = list(state.balances)
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
yield 'pre', state
spec.process_slashings(state)
yield 'post', state
total_penalties = sum(state.slashings)
for i in slashed_indices:
v = state.validators[i]
expected_penalty = (
v.effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT
* (3 * total_penalties)
// (total_balance)
* spec.EFFECTIVE_BALANCE_INCREMENT
)
assert state.balances[i] == pre_slash_balances[i] - expected_penalty

View File

@ -1,7 +1,13 @@
from eth2spec.test.helpers.custody import get_valid_early_derived_secret_reveal
from eth2spec.test.helpers.block import apply_empty_block
from eth2spec.test.helpers.state import next_epoch, get_balance
from eth2spec.test.context import with_all_phases_except, spec_state_test, expect_assertion_error
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
expect_assertion_error,
always_bls,
never_bls,
)
def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, valid=True):
@ -24,7 +30,7 @@ def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, v
spec.process_early_derived_secret_reveal(state, randao_key_reveal)
slashed_validator = state.validator_registry[randao_key_reveal.revealed_index]
slashed_validator = state.validators[randao_key_reveal.revealed_index]
if randao_key_reveal.epoch >= spec.get_current_epoch(state) + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING:
assert slashed_validator.slashed
@ -36,6 +42,7 @@ def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, v
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_success(spec, state):
randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state)
@ -44,6 +51,7 @@ def test_success(spec, state):
@with_all_phases_except(['phase0'])
@never_bls
@spec_state_test
def test_reveal_from_current_epoch(spec, state):
randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state))
@ -52,6 +60,7 @@ def test_reveal_from_current_epoch(spec, state):
@with_all_phases_except(['phase0'])
@never_bls
@spec_state_test
def test_reveal_from_past_epoch(spec, state):
next_epoch(spec, state)
@ -62,6 +71,7 @@ def test_reveal_from_past_epoch(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_reveal_with_custody_padding(spec, state):
randao_key_reveal = get_valid_early_derived_secret_reveal(
@ -73,6 +83,7 @@ def test_reveal_with_custody_padding(spec, state):
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_reveal_with_custody_padding_minus_one(spec, state):
randao_key_reveal = get_valid_early_derived_secret_reveal(
@ -84,6 +95,7 @@ def test_reveal_with_custody_padding_minus_one(spec, state):
@with_all_phases_except(['phase0'])
@never_bls
@spec_state_test
def test_double_reveal(spec, state):
randao_key_reveal1 = get_valid_early_derived_secret_reveal(
@ -108,15 +120,17 @@ def test_double_reveal(spec, state):
@with_all_phases_except(['phase0'])
@never_bls
@spec_state_test
def test_revealer_is_slashed(spec, state):
randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state))
state.validator_registry[randao_key_reveal.revealed_index].slashed = True
state.validators[randao_key_reveal.revealed_index].slashed = True
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False)
@with_all_phases_except(['phase0'])
@never_bls
@spec_state_test
def test_far_future_epoch(spec, state):
randao_key_reveal = get_valid_early_derived_secret_reveal(

View File

@ -0,0 +1,26 @@
from eth2spec.test.helpers.phase1.shard_block import (
build_empty_shard_block,
)
from eth2spec.test.context import (
with_all_phases_except,
spec_state_test,
always_bls,
)
@with_all_phases_except(['phase0'])
@always_bls
@spec_state_test
def test_is_valid_shard_block(spec, state):
block = build_empty_shard_block(
spec,
state,
slot=spec.Slot(spec.PERSISTENT_COMMITTEE_PERIOD * 100),
shard=spec.Shard(1),
parent_root=spec.Hash(),
signed=True,
)
# TODO: test `is_valid_shard_block`
yield 'blocks', (block,)

View File

@ -1,23 +1,52 @@
from copy import deepcopy
from typing import List
from eth2spec.utils.ssz.ssz_impl import signing_root
from eth2spec.utils.bls import bls_sign
from eth2spec.test.helpers.state import get_balance
# from eth2spec.test.helpers.transfers import get_valid_transfer
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block
from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.context import spec_state_test, never_bls, with_all_phases
from eth2spec.test.context import spec_state_test, with_all_phases, expect_assertion_error
@with_all_phases
@spec_state_test
def test_prev_slot_block_transition(spec, state):
# Go to clean slot
spec.process_slots(state, state.slot + 1)
# Make a block for it
block = build_empty_block(spec, state, slot=state.slot, signed=True)
# Transition to next slot, above block will not be invalid on top of new state.
spec.process_slots(state, state.slot + 1)
yield 'pre', state
expect_assertion_error(lambda: state_transition_and_sign_block(spec, state, block))
yield 'blocks', [block]
yield 'post', None
@with_all_phases
@spec_state_test
def test_same_slot_block_transition(spec, state):
# Same slot on top of pre-state, but move out of slot 0 first.
spec.process_slots(state, state.slot + 1)
block = build_empty_block(spec, state, slot=state.slot, signed=True)
yield 'pre', state
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block]
yield 'post', state
@with_all_phases
@never_bls
@spec_state_test
def test_empty_block_transition(spec, state):
pre_slot = state.slot
@ -26,17 +55,34 @@ def test_empty_block_transition(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state, signed=True)
yield 'blocks', [block], List[spec.BeaconBlock]
spec.state_transition(state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block]
yield 'post', state
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
assert spec.get_block_root_at_slot(state, pre_slot) == block.parent_root
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Hash()
@with_all_phases
@spec_state_test
def test_invalid_state_root(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
block.state_root = b"\xaa" * 32
sign_block(spec, state, block)
expect_assertion_error(
lambda: spec.state_transition(state, block, validate_state_root=True))
yield 'blocks', [block]
yield 'post', None
@with_all_phases
@never_bls
@spec_state_test
def test_skipped_slots(spec, state):
pre_slot = state.slot
@ -45,12 +91,14 @@ def test_skipped_slots(spec, state):
block = build_empty_block_for_next_slot(spec, state)
block.slot += 3
sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
spec.state_transition(state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block]
yield 'post', state
assert state.slot == block.slot
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Hash()
for slot in range(pre_slot, state.slot):
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
@ -64,9 +112,10 @@ def test_empty_epoch_transition(spec, state):
block = build_empty_block_for_next_slot(spec, state)
block.slot += spec.SLOTS_PER_EPOCH
sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
spec.state_transition(state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block]
yield 'post', state
assert state.slot == block.slot
@ -74,25 +123,29 @@ def test_empty_epoch_transition(spec, state):
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
# @with_all_phases
# @spec_state_test
# def test_empty_epoch_transition_not_finalizing(spec, state):
# # copy for later balance lookups.
# pre_state = deepcopy(state)
# yield 'pre', state
@with_all_phases
@spec_state_test
def test_empty_epoch_transition_not_finalizing(spec, state):
# Don't run for non-minimal configs, it takes very long, and the effect
# of calling finalization/justification is just the same as with the minimal configuration.
if spec.SLOTS_PER_EPOCH > 8:
return
# block = build_empty_block_for_next_slot(spec, state)
# block.slot += spec.SLOTS_PER_EPOCH * 5
# sign_block(spec, state, block, proposer_index=0)
# yield 'blocks', [block], List[spec.BeaconBlock]
# copy for later balance lookups.
pre_balances = list(state.balances)
yield 'pre', state
# spec.state_transition(state, block)
# yield 'post', state
spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH * 5))
block = build_empty_block_for_next_slot(spec, state, signed=True)
state_transition_and_sign_block(spec, state, block)
# assert state.slot == block.slot
# assert state.finalized_epoch < spec.get_current_epoch(state) - 4
# for index in range(len(state.validator_registry)):
# assert get_balance(state, index) < get_balance(pre_state, index)
yield 'blocks', [block]
yield 'post', state
assert state.slot == block.slot
assert state.finalized_checkpoint.epoch < spec.get_current_epoch(state) - 4
for index in range(len(state.validators)):
assert state.balances[index] < pre_balances[index]
@with_all_phases
@ -103,7 +156,7 @@ def test_proposer_slashing(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
validator_index = proposer_slashing.proposer_index
assert not state.validator_registry[validator_index].slashed
assert not state.validators[validator_index].slashed
yield 'pre', state
@ -113,13 +166,14 @@ def test_proposer_slashing(spec, state):
block = build_empty_block_for_next_slot(spec, state)
block.body.proposer_slashings.append(proposer_slashing)
sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
spec.state_transition(state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block]
yield 'post', state
# check if slashed
slashed_validator = state.validator_registry[validator_index]
slashed_validator = state.validators[validator_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
@ -134,10 +188,10 @@ def test_attester_slashing(spec, state):
pre_state = deepcopy(state)
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
validator_index = (attester_slashing.attestation_1.custody_bit_0_indices +
attester_slashing.attestation_1.custody_bit_1_indices)[0]
validator_index = (attester_slashing.attestation_1.custody_bit_0_indices
+ attester_slashing.attestation_1.custody_bit_1_indices)[0]
assert not state.validator_registry[validator_index].slashed
assert not state.validators[validator_index].slashed
yield 'pre', state
@ -147,12 +201,13 @@ def test_attester_slashing(spec, state):
block = build_empty_block_for_next_slot(spec, state)
block.body.attester_slashings.append(attester_slashing)
sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
spec.state_transition(state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block]
yield 'post', state
slashed_validator = state.validator_registry[validator_index]
slashed_validator = state.validators[validator_index]
assert slashed_validator.slashed
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
@ -167,15 +222,35 @@ def test_attester_slashing(spec, state):
)
# TODO update functions below to be like above, i.e. with @spec_state_test and yielding data to put into the test vector
@with_all_phases
@spec_state_test
def test_expected_deposit_in_block(spec, state):
# Make the state expect a deposit, then don't provide it.
state.eth1_data.deposit_count += 1
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
sign_block(spec, state, block)
bad = False
try:
state_transition_and_sign_block(spec, state, block)
bad = True
except AssertionError:
pass
if bad:
raise AssertionError("expected deposit was not enforced")
yield 'blocks', [block]
yield 'post', None
@with_all_phases
@spec_state_test
def test_deposit_in_block(spec, state):
initial_registry_len = len(state.validator_registry)
initial_registry_len = len(state.validators)
initial_balances_len = len(state.balances)
validator_index = len(state.validator_registry)
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
@ -185,15 +260,15 @@ def test_deposit_in_block(spec, state):
block.body.deposits.append(deposit)
sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
state_transition_and_sign_block(spec, state, block)
spec.state_transition(state, block)
yield 'blocks', [block]
yield 'post', state
assert len(state.validator_registry) == initial_registry_len + 1
assert len(state.validators) == initial_registry_len + 1
assert len(state.balances) == initial_balances_len + 1
assert get_balance(state, validator_index) == spec.MAX_EFFECTIVE_BALANCE
assert state.validator_registry[validator_index].pubkey == pubkeys[validator_index]
assert state.validators[validator_index].pubkey == pubkeys[validator_index]
@with_all_phases
@ -203,7 +278,7 @@ def test_deposit_top_up(spec, state):
amount = spec.MAX_EFFECTIVE_BALANCE // 4
deposit = prepare_state_and_deposit(spec, state, validator_index, amount)
initial_registry_len = len(state.validator_registry)
initial_registry_len = len(state.validators)
initial_balances_len = len(state.balances)
validator_pre_balance = get_balance(state, validator_index)
@ -213,12 +288,12 @@ def test_deposit_top_up(spec, state):
block.body.deposits.append(deposit)
sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
state_transition_and_sign_block(spec, state, block)
spec.state_transition(state, block)
yield 'blocks', [block]
yield 'post', state
assert len(state.validator_registry) == initial_registry_len
assert len(state.validators) == initial_registry_len
assert len(state.balances) == initial_balances_len
assert get_balance(state, validator_index) == validator_pre_balance + amount
@ -238,7 +313,7 @@ def test_attestation(spec, state):
attestation_block.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
attestation_block.body.attestations.append(attestation)
sign_block(spec, state, attestation_block)
spec.state_transition(state, attestation_block)
state_transition_and_sign_block(spec, state, attestation_block)
assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1
@ -248,9 +323,9 @@ def test_attestation(spec, state):
epoch_block = build_empty_block_for_next_slot(spec, state)
epoch_block.slot += spec.SLOTS_PER_EPOCH
sign_block(spec, state, epoch_block)
spec.state_transition(state, epoch_block)
state_transition_and_sign_block(spec, state, epoch_block)
yield 'blocks', [attestation_block, epoch_block], List[spec.BeaconBlock]
yield 'blocks', [attestation_block, epoch_block]
yield 'post', state
assert len(state.current_epoch_attestations) == 0
@ -287,20 +362,20 @@ def test_voluntary_exit(spec, state):
initiate_exit_block = build_empty_block_for_next_slot(spec, state)
initiate_exit_block.body.voluntary_exits.append(voluntary_exit)
sign_block(spec, state, initiate_exit_block)
spec.state_transition(state, initiate_exit_block)
state_transition_and_sign_block(spec, state, initiate_exit_block)
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
# Process within epoch transition
exit_block = build_empty_block_for_next_slot(spec, state)
exit_block.slot += spec.SLOTS_PER_EPOCH
sign_block(spec, state, exit_block)
spec.state_transition(state, exit_block)
state_transition_and_sign_block(spec, state, exit_block)
yield 'blocks', [initiate_exit_block, exit_block], List[spec.BeaconBlock]
yield 'blocks', [initiate_exit_block, exit_block]
yield 'post', state
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
# @with_all_phases
@ -317,7 +392,7 @@ def test_voluntary_exit(spec, state):
# pre_transfer_recipient_balance = get_balance(state, recipient_index)
# un-activate so validator can transfer
# state.validator_registry[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
# state.validators[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH
# yield 'pre', state
@ -326,9 +401,9 @@ def test_voluntary_exit(spec, state):
# block.body.transfers.append(transfer)
# sign_block(spec, state, block)
# yield 'blocks', [block], List[spec.BeaconBlock]
# state_transition_and_sign_block(spec, state, block)
# spec.state_transition(state, block)
# yield 'blocks', [block]
# yield 'post', state
# sender_balance = get_balance(state, sender_index)
@ -343,10 +418,10 @@ def test_balance_driven_status_transitions(spec, state):
current_epoch = spec.get_current_epoch(state)
validator_index = spec.get_active_validator_indices(state, current_epoch)[-1]
assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
# set validator balance to below ejection threshold
state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE
state.validators[validator_index].effective_balance = spec.EJECTION_BALANCE
yield 'pre', state
@ -354,12 +429,12 @@ def test_balance_driven_status_transitions(spec, state):
block = build_empty_block_for_next_slot(spec, state)
block.slot += spec.SLOTS_PER_EPOCH
sign_block(spec, state, block)
spec.state_transition(state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
yield 'blocks', [block]
yield 'post', state
assert state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@with_all_phases
@ -371,9 +446,10 @@ def test_historical_batch(spec, state):
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state, signed=True)
spec.state_transition(state, block)
sign_block(spec, state, block)
state_transition_and_sign_block(spec, state, block)
yield 'blocks', [block], List[spec.BeaconBlock]
yield 'blocks', [block]
yield 'post', state
assert state.slot == block.slot
@ -381,29 +457,78 @@ def test_historical_batch(spec, state):
assert len(state.historical_roots) == pre_historical_roots_len + 1
# @with_all_phases
# @spec_state_test
# def test_eth1_data_votes(spec, state):
# yield 'pre', state
@with_all_phases
@spec_state_test
def test_eth1_data_votes_consensus(spec, state):
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
return
# expected_votes = 0
# assert len(state.eth1_data_votes) == expected_votes
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
sign_block(spec, state, offset_block)
state_transition_and_sign_block(spec, state, offset_block)
yield 'pre', state
# blocks = []
# for _ in range(spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1):
# block = build_empty_block_for_next_slot(spec, state)
# spec.state_transition(state, block)
# expected_votes += 1
# assert len(state.eth1_data_votes) == expected_votes
# blocks.append(block)
a = b'\xaa' * 32
b = b'\xbb' * 32
c = b'\xcc' * 32
# block = build_empty_block_for_next_slot(spec, state)
# blocks.append(block)
blocks = []
# spec.state_transition(state, block)
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
block = build_empty_block_for_next_slot(spec, state)
# wait for over 50% for A, then start voting B
block.body.eth1_data.block_hash = b if i * 2 > spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
sign_block(spec, state, block)
state_transition_and_sign_block(spec, state, block)
blocks.append(block)
# yield 'blocks', [block], List[spec.BeaconBlock]
# yield 'post', state
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
assert state.eth1_data.block_hash == a
# assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
# assert len(state.eth1_data_votes) == 1
# transition to next eth1 voting period
block = build_empty_block_for_next_slot(spec, state)
block.body.eth1_data.block_hash = c
sign_block(spec, state, block)
state_transition_and_sign_block(spec, state, block)
blocks.append(block)
yield 'blocks', blocks
yield 'post', state
assert state.eth1_data.block_hash == a
assert state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0
assert len(state.eth1_data_votes) == 1
assert state.eth1_data_votes[0].block_hash == c
@with_all_phases
@spec_state_test
def test_eth1_data_votes_no_consensus(spec, state):
# Don't run when it will take very, very long to simulate. Minimal configuration suffices.
if spec.SLOTS_PER_ETH1_VOTING_PERIOD > 16:
return
offset_block = build_empty_block(spec, state, slot=spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1)
sign_block(spec, state, offset_block)
state_transition_and_sign_block(spec, state, offset_block)
yield 'pre', state
a = b'\xaa' * 32
b = b'\xbb' * 32
blocks = []
for i in range(0, spec.SLOTS_PER_ETH1_VOTING_PERIOD):
block = build_empty_block_for_next_slot(spec, state)
# wait for precisely 50% for A, then start voting B for other 50%
block.body.eth1_data.block_hash = b if i * 2 >= spec.SLOTS_PER_ETH1_VOTING_PERIOD else a
sign_block(spec, state, block)
state_transition_and_sign_block(spec, state, block)
blocks.append(block)
assert len(state.eth1_data_votes) == spec.SLOTS_PER_ETH1_VOTING_PERIOD
assert state.eth1_data.block_hash == b'\x00' * 32
yield 'blocks', blocks
yield 'post', state

View File

@ -1,10 +1,6 @@
from copy import deepcopy
from typing import List
from eth2spec.test.context import spec_state_test, never_bls, with_all_phases
from eth2spec.test.helpers.state import next_epoch
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, apply_empty_block
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.state import next_epoch, next_epoch_with_attestations
from eth2spec.test.helpers.block import apply_empty_block
def check_finality(spec,
@ -14,60 +10,34 @@ def check_finality(spec,
previous_justified_changed,
finalized_changed):
if current_justified_changed:
assert state.current_justified_epoch > prev_state.current_justified_epoch
assert state.current_justified_root != prev_state.current_justified_root
assert state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch
assert state.current_justified_checkpoint.root != prev_state.current_justified_checkpoint.root
else:
assert state.current_justified_epoch == prev_state.current_justified_epoch
assert state.current_justified_root == prev_state.current_justified_root
assert state.current_justified_checkpoint == prev_state.current_justified_checkpoint
if previous_justified_changed:
assert state.previous_justified_epoch > prev_state.previous_justified_epoch
assert state.previous_justified_root != prev_state.previous_justified_root
assert state.previous_justified_checkpoint.epoch > prev_state.previous_justified_checkpoint.epoch
assert state.previous_justified_checkpoint.root != prev_state.previous_justified_checkpoint.root
else:
assert state.previous_justified_epoch == prev_state.previous_justified_epoch
assert state.previous_justified_root == prev_state.previous_justified_root
assert state.previous_justified_checkpoint == prev_state.previous_justified_checkpoint
if finalized_changed:
assert state.finalized_epoch > prev_state.finalized_epoch
assert state.finalized_root != prev_state.finalized_root
assert state.finalized_checkpoint.epoch > prev_state.finalized_checkpoint.epoch
assert state.finalized_checkpoint.root != prev_state.finalized_checkpoint.root
else:
assert state.finalized_epoch == prev_state.finalized_epoch
assert state.finalized_root == prev_state.finalized_root
def next_epoch_with_attestations(spec,
state,
fill_cur_epoch,
fill_prev_epoch):
post_state = deepcopy(state)
blocks = []
for _ in range(spec.SLOTS_PER_EPOCH):
block = build_empty_block_for_next_slot(spec, post_state)
if fill_cur_epoch:
slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
if slot_to_attest >= spec.get_epoch_start_slot(spec.get_current_epoch(post_state)):
cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest)
block.body.attestations.append(cur_attestation)
if fill_prev_epoch:
slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
prev_attestation = get_valid_attestation(spec, post_state, slot_to_attest)
block.body.attestations.append(prev_attestation)
spec.state_transition(post_state, block)
blocks.append(block)
return state, blocks, post_state
assert state.finalized_checkpoint == prev_state.finalized_checkpoint
@with_all_phases
@never_bls
@spec_state_test
def test_finality_rule_4(spec, state):
def test_finality_no_updates_at_genesis(spec, state):
assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH
yield 'pre', state
blocks = []
for epoch in range(4):
for epoch in range(2):
prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, True, False)
blocks += new_blocks
@ -77,15 +47,36 @@ def test_finality_rule_4(spec, state):
# justification/finalization skipped at GENESIS_EPOCH + 1
elif epoch == 1:
check_finality(spec, state, prev_state, False, False, False)
elif epoch == 2:
yield 'blocks', blocks
yield 'post', state
@with_all_phases
@never_bls
@spec_state_test
def test_finality_rule_4(spec, state):
# get past first two epochs that finality does not run on
next_epoch(spec, state)
apply_empty_block(spec, state)
next_epoch(spec, state)
apply_empty_block(spec, state)
yield 'pre', state
blocks = []
for epoch in range(2):
prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, True, False)
blocks += new_blocks
if epoch == 0:
check_finality(spec, state, prev_state, True, False, False)
elif epoch >= 3:
elif epoch == 1:
# rule 4 of finality
check_finality(spec, state, prev_state, True, True, True)
assert state.finalized_epoch == prev_state.current_justified_epoch
assert state.finalized_root == prev_state.current_justified_root
assert state.finalized_checkpoint == prev_state.current_justified_checkpoint
yield 'blocks', blocks, List[spec.BeaconBlock]
yield 'blocks', blocks
yield 'post', state
@ -113,10 +104,9 @@ def test_finality_rule_1(spec, state):
elif epoch == 2:
# finalized by rule 1
check_finality(spec, state, prev_state, True, True, True)
assert state.finalized_epoch == prev_state.previous_justified_epoch
assert state.finalized_root == prev_state.previous_justified_root
assert state.finalized_checkpoint == prev_state.previous_justified_checkpoint
yield 'blocks', blocks, List[spec.BeaconBlock]
yield 'blocks', blocks
yield 'post', state
@ -144,12 +134,11 @@ def test_finality_rule_2(spec, state):
prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, False, True)
# finalized by rule 2
check_finality(spec, state, prev_state, True, False, True)
assert state.finalized_epoch == prev_state.previous_justified_epoch
assert state.finalized_root == prev_state.previous_justified_root
assert state.finalized_checkpoint == prev_state.previous_justified_checkpoint
blocks += new_blocks
yield 'blocks', blocks, List[spec.BeaconBlock]
yield 'blocks', blocks
yield 'post', state
@ -196,8 +185,7 @@ def test_finality_rule_3(spec, state):
blocks += new_blocks
# rule 3
check_finality(spec, state, prev_state, True, True, True)
assert state.finalized_epoch == prev_state.current_justified_epoch
assert state.finalized_root == prev_state.current_justified_root
assert state.finalized_checkpoint == prev_state.current_justified_checkpoint
yield 'blocks', blocks, List[spec.BeaconBlock]
yield 'blocks', blocks
yield 'post', state

View File

@ -1,6 +1,6 @@
from typing import Dict, Any, Callable, Iterable
from eth2spec.debug.encode import encode
from eth2spec.utils.ssz.ssz_typing import Container
from eth2spec.utils.ssz.ssz_typing import SSZValue
def spectest(description: str = None):
@ -29,10 +29,12 @@ def spectest(description: str = None):
(key, value, typ) = data
out[key] = encode(value, typ)
else:
# Otherwise, try to infer the type, but keep it as-is if it's not a SSZ container.
# Otherwise, try to infer the type, but keep it as-is if it's not a SSZ type or bytes.
(key, value) = data
if isinstance(value, Container):
out[key] = encode(value, value.__class__)
if isinstance(value, (SSZValue, bytes)):
out[key] = encode(value)
elif isinstance(value, list) and all([isinstance(el, (SSZValue, bytes)) for el in value]):
out[key] = [encode(el) for el in value]
else:
# not a ssz value.
# It could be vector or bytes still, but it is a rare case,

View File

@ -23,12 +23,14 @@ def only_with_bls(alt_return=None):
@only_with_bls(alt_return=True)
def bls_verify(pubkey, message_hash, signature, domain):
return bls.verify(message_hash=message_hash, pubkey=pubkey, signature=signature, domain=domain)
return bls.verify(message_hash=message_hash, pubkey=pubkey,
signature=signature, domain=domain)
@only_with_bls(alt_return=True)
def bls_verify_multiple(pubkeys, message_hashes, signature, domain):
return bls.verify_multiple(pubkeys, message_hashes, signature, domain)
return bls.verify_multiple(pubkeys=pubkeys, message_hashes=message_hashes,
signature=signature, domain=domain)
@only_with_bls(alt_return=STUB_PUBKEY)
@ -43,4 +45,5 @@ def bls_aggregate_signatures(signatures):
@only_with_bls(alt_return=STUB_SIGNATURE)
def bls_sign(message_hash, privkey, domain):
return bls.sign(message_hash=message_hash, privkey=privkey, domain=domain)
return bls.sign(message_hash=message_hash, privkey=privkey,
domain=domain)

View File

@ -1,18 +1,18 @@
from .hash_function import hash
from eth2spec.utils.hash_function import hash
from math import log2
ZERO_BYTES32 = b'\x00' * 32
zerohashes = [ZERO_BYTES32]
for layer in range(1, 32):
for layer in range(1, 100):
zerohashes.append(hash(zerohashes[layer - 1] + zerohashes[layer - 1]))
# Compute a Merkle root of a right-zerobyte-padded 2**32 sized tree
def calc_merkle_tree_from_leaves(values):
def calc_merkle_tree_from_leaves(values, layer_count=32):
values = list(values)
tree = [values[::]]
for h in range(32):
for h in range(layer_count):
if len(values) % 2 == 1:
values.append(zerohashes[h])
values = [hash(values[i] + values[i + 1]) for i in range(0, len(values), 2)]
@ -20,8 +20,13 @@ def calc_merkle_tree_from_leaves(values):
return tree
def get_merkle_root(values):
return calc_merkle_tree_from_leaves(values)[-1][0]
def get_merkle_root(values, pad_to=1):
if pad_to == 0:
return zerohashes[0]
layer_count = int(log2(pad_to))
if len(values) == 0:
return zerohashes[layer_count]
return calc_merkle_tree_from_leaves(values, layer_count)[-1][0]
def get_merkle_proof(tree, item_index):
@ -32,23 +37,46 @@ def get_merkle_proof(tree, item_index):
return proof
def next_power_of_two(v: int) -> int:
"""
Get the next power of 2. (for 64 bit range ints).
0 is a special case, to have non-empty defaults.
Examples:
0 -> 1, 1 -> 1, 2 -> 2, 3 -> 4, 32 -> 32, 33 -> 64
"""
if v == 0:
return 1
return 1 << (v - 1).bit_length()
def merkleize_chunks(chunks, limit=None):
# If no limit is defined, we are just merkleizing chunks (e.g. SSZ container).
if limit is None:
limit = len(chunks)
count = len(chunks)
# See if the input is within expected size.
# If not, a list-limit is set incorrectly, or a value is unexpectedly large.
assert count <= limit
def merkleize_chunks(chunks):
tree = chunks[::]
margin = next_power_of_two(len(chunks)) - len(chunks)
tree.extend([ZERO_BYTES32] * margin)
tree = [ZERO_BYTES32] * len(tree) + tree
for i in range(len(tree) // 2 - 1, 0, -1):
tree[i] = hash(tree[i * 2] + tree[i * 2 + 1])
return tree[1]
if limit == 0:
return zerohashes[0]
depth = max(count - 1, 0).bit_length()
max_depth = (limit - 1).bit_length()
tmp = [None for _ in range(max_depth + 1)]
def merge(h, i):
j = 0
while True:
if i & (1 << j) == 0:
if i == count and j < depth:
h = hash(h + zerohashes[j]) # keep going if we are complementing the void to the next power of 2
else:
break
else:
h = hash(tmp[j] + h)
j += 1
tmp[j] = h
# merge in leaf by leaf.
for i in range(count):
merge(chunks[i], i)
# complement with 0 if empty, or if not the right power of 2
if 1 << depth != count:
merge(zerohashes[0], count)
# the next power of two may be smaller than the ultimate virtual size, complement with zero-hashes at each depth.
for j in range(depth, max_depth):
tmp[j + 1] = hash(tmp[j] + zerohashes[j])
return tmp[max_depth]

View File

@ -1,11 +1,8 @@
from ..merkle_minimal import merkleize_chunks, hash
from eth2spec.utils.ssz.ssz_typing import (
is_uint_type, is_bool_type, is_container_type,
is_list_kind, is_vector_kind,
read_vector_elem_type, read_elem_type,
uint_byte_size,
infer_input_type,
get_zero_value,
from ..merkle_minimal import merkleize_chunks
from ..hash_function import hash
from .ssz_typing import (
SSZValue, SSZType, BasicValue, BasicType, Series, Elements, Bits, boolean, Container, List, Bytes,
Bitlist, Bitvector, uint,
)
# SSZ Serialization
@ -14,68 +11,56 @@ from eth2spec.utils.ssz.ssz_typing import (
BYTES_PER_LENGTH_OFFSET = 4
def is_basic_type(typ):
return is_uint_type(typ) or is_bool_type(typ)
def serialize_basic(value, typ):
if is_uint_type(typ):
return value.to_bytes(uint_byte_size(typ), 'little')
elif is_bool_type(typ):
def serialize_basic(value: SSZValue):
if isinstance(value, uint):
return value.to_bytes(value.type().byte_len, 'little')
elif isinstance(value, boolean):
if value:
return b'\x01'
else:
return b'\x00'
else:
raise Exception("Type not supported: {}".format(typ))
raise Exception(f"Type not supported: {type(value)}")
def deserialize_basic(value, typ):
if is_uint_type(typ):
def deserialize_basic(value, typ: BasicType):
if issubclass(typ, uint):
return typ(int.from_bytes(value, 'little'))
elif is_bool_type(typ):
elif issubclass(typ, boolean):
assert value in (b'\x00', b'\x01')
return True if value == b'\x01' else False
return typ(value == b'\x01')
else:
raise Exception("Type not supported: {}".format(typ))
raise Exception(f"Type not supported: {typ}")
def is_fixed_size(typ):
if is_basic_type(typ):
return True
elif is_list_kind(typ):
return False
elif is_vector_kind(typ):
return is_fixed_size(read_vector_elem_type(typ))
elif is_container_type(typ):
return all(is_fixed_size(t) for t in typ.get_field_types())
def is_empty(obj: SSZValue):
return type(obj).default() == obj
def serialize(obj: SSZValue):
if isinstance(obj, BasicValue):
return serialize_basic(obj)
elif isinstance(obj, Bitvector):
return obj.as_bytes()
elif isinstance(obj, Bitlist):
as_bytearray = list(obj.as_bytes())
if len(obj) % 8 == 0:
as_bytearray.append(1)
else:
raise Exception("Type not supported: {}".format(typ))
def is_empty(obj):
return get_zero_value(type(obj)) == obj
@infer_input_type
def serialize(obj, typ=None):
if is_basic_type(typ):
return serialize_basic(obj, typ)
elif is_list_kind(typ) or is_vector_kind(typ):
return encode_series(obj, [read_elem_type(typ)] * len(obj))
elif is_container_type(typ):
return encode_series(obj.get_field_values(), typ.get_field_types())
as_bytearray[len(obj) // 8] |= 1 << (len(obj) % 8)
return bytes(as_bytearray)
elif isinstance(obj, Series):
return encode_series(obj)
else:
raise Exception("Type not supported: {}".format(typ))
raise Exception(f"Type not supported: {type(obj)}")
def encode_series(values, types):
# bytes and bytesN are already in the right format.
if isinstance(values, bytes):
def encode_series(values: Series):
if isinstance(values, bytes): # Bytes and BytesN are already like serialized output
return values
# Recursively serialize
parts = [(is_fixed_size(types[i]), serialize(values[i], typ=types[i])) for i in range(len(values))]
parts = [(v.type().is_fixed_size(), serialize(v)) for v in values]
# Compute and check lengths
fixed_lengths = [len(serialized) if constant_size else BYTES_PER_LENGTH_OFFSET
@ -107,10 +92,14 @@ def encode_series(values, types):
# -----------------------------
def pack(values, subtype):
if isinstance(values, bytes):
def pack(values: Series):
if isinstance(values, bytes): # Bytes and BytesN are already packed
return values
return b''.join([serialize_basic(value, subtype) for value in values])
elif isinstance(values, Bits):
# packs the bits in bytes, left-aligned.
# Exclusive length delimiting bits for bitlists.
return values.as_bytes()
return b''.join([serialize_basic(value) for value in values])
def chunkify(bytez):
@ -123,41 +112,53 @@ def mix_in_length(root, length):
return hash(root + length.to_bytes(32, 'little'))
def is_bottom_layer_kind(typ):
def is_bottom_layer_kind(typ: SSZType):
return (
is_basic_type(typ) or
(is_list_kind(typ) or is_vector_kind(typ)) and is_basic_type(read_elem_type(typ))
isinstance(typ, BasicType) or
(issubclass(typ, Elements) and isinstance(typ.elem_type, BasicType))
)
@infer_input_type
def get_typed_values(obj, typ=None):
if is_container_type(typ):
return obj.get_typed_values()
elif is_list_kind(typ) or is_vector_kind(typ):
elem_type = read_elem_type(typ)
return list(zip(obj, [elem_type] * len(obj)))
def item_length(typ: SSZType) -> int:
if issubclass(typ, BasicValue):
return typ.byte_len
else:
raise Exception("Invalid type")
return 32
@infer_input_type
def hash_tree_root(obj, typ=None):
if is_bottom_layer_kind(typ):
data = serialize_basic(obj, typ) if is_basic_type(typ) else pack(obj, read_elem_type(typ))
leaves = chunkify(data)
def chunk_count(typ: SSZType) -> int:
# note that for lists, .length *on the type* describes the list limit.
if isinstance(typ, BasicType):
return 1
elif issubclass(typ, Bits):
return (typ.length + 255) // 256
elif issubclass(typ, Elements):
return (typ.length * item_length(typ.elem_type) + 31) // 32
elif issubclass(typ, Container):
return len(typ.get_fields())
else:
fields = get_typed_values(obj, typ=typ)
leaves = [hash_tree_root(field_value, typ=field_typ) for field_value, field_typ in fields]
if is_list_kind(typ):
return mix_in_length(merkleize_chunks(leaves), len(obj))
raise Exception(f"Type not supported: {typ}")
def hash_tree_root(obj: SSZValue):
if isinstance(obj, Series):
if is_bottom_layer_kind(obj.type()):
leaves = chunkify(pack(obj))
else:
leaves = [hash_tree_root(value) for value in obj]
elif isinstance(obj, BasicValue):
leaves = chunkify(serialize_basic(obj))
else:
raise Exception(f"Type not supported: {type(obj)}")
if isinstance(obj, (List, Bytes, Bitlist)):
return mix_in_length(merkleize_chunks(leaves, limit=chunk_count(obj.type())), len(obj))
else:
return merkleize_chunks(leaves)
@infer_input_type
def signing_root(obj, typ):
assert is_container_type(typ)
def signing_root(obj: Container):
# ignore last field
leaves = [hash_tree_root(field_value, typ=field_typ) for field_value, field_typ in obj.get_typed_values()[:-1]]
fields = [field for field in obj][:-1]
leaves = [hash_tree_root(f) for f in fields]
return merkleize_chunks(chunkify(b''.join(leaves)))

View File

@ -1,136 +1,183 @@
from typing import List, Iterable, TypeVar, Type, NewType
from typing import Union
from typing_inspect import get_origin
# SSZ integers
# -----------------------------
from typing import Dict, Iterator
import copy
from types import GeneratorType
class uint(int):
class DefaultingTypeMeta(type):
def default(cls):
raise Exception("Not implemented")
class SSZType(DefaultingTypeMeta):
def is_fixed_size(cls):
raise Exception("Not implemented")
class SSZValue(object, metaclass=SSZType):
def type(self):
return self.__class__
class BasicType(SSZType):
byte_len = 0
def __new__(cls, value, *args, **kwargs):
def is_fixed_size(cls):
return True
class BasicValue(int, SSZValue, metaclass=BasicType):
pass
class boolean(BasicValue): # can't subclass bool.
byte_len = 1
def __new__(cls, value: int): # int value, but can be any subclass of int (bool, Bit, Bool, etc...)
if value < 0 or value > 1:
raise ValueError(f"value {value} out of bounds for bit")
return super().__new__(cls, value)
@classmethod
def default(cls):
return cls(0)
def __bool__(self):
return self > 0
# Alias for Bool
class bit(boolean):
pass
class uint(BasicValue, metaclass=BasicType):
def __new__(cls, value: int):
if value < 0:
raise ValueError("unsigned types must not be negative")
if cls.byte_len and value.bit_length() > (cls.byte_len << 3):
raise ValueError("value out of bounds for uint{}".format(cls.byte_len * 8))
return super().__new__(cls, value)
def __add__(self, other):
return self.__class__(super().__add__(coerce_type_maybe(other, self.__class__, strict=True)))
def __sub__(self, other):
return self.__class__(super().__sub__(coerce_type_maybe(other, self.__class__, strict=True)))
@classmethod
def default(cls):
return cls(0)
class uint8(uint):
byte_len = 1
def __new__(cls, value, *args, **kwargs):
if value.bit_length() > 8:
raise ValueError("value out of bounds for uint8")
return super().__new__(cls, value)
# Alias for uint8
byte = NewType('byte', uint8)
class byte(uint8):
pass
class uint16(uint):
byte_len = 2
def __new__(cls, value, *args, **kwargs):
if value.bit_length() > 16:
raise ValueError("value out of bounds for uint16")
return super().__new__(cls, value)
class uint32(uint):
byte_len = 4
def __new__(cls, value, *args, **kwargs):
if value.bit_length() > 32:
raise ValueError("value out of bounds for uint16")
return super().__new__(cls, value)
# We simply default to uint64. But do give it a name, for readability
uint64 = NewType('uint64', int)
class uint64(uint):
byte_len = 8
class uint128(uint):
byte_len = 16
def __new__(cls, value, *args, **kwargs):
if value.bit_length() > 128:
raise ValueError("value out of bounds for uint128")
return super().__new__(cls, value)
class uint256(uint):
byte_len = 32
def __new__(cls, value, *args, **kwargs):
if value.bit_length() > 256:
raise ValueError("value out of bounds for uint256")
return super().__new__(cls, value)
def is_uint_type(typ):
# All integers are uint in the scope of the spec here.
# Since we default to uint64. Bounds can be checked elsewhere.
# However, some are wrapped in a NewType
if hasattr(typ, '__supertype__'):
# get the type that the NewType is wrapping
typ = typ.__supertype__
return isinstance(typ, type) and issubclass(typ, int) and not issubclass(typ, bool)
def uint_byte_size(typ):
if hasattr(typ, '__supertype__'):
typ = typ.__supertype__
if isinstance(typ, type):
if issubclass(typ, uint):
return typ.byte_len
elif issubclass(typ, int):
# Default to uint64
return 8
def coerce_type_maybe(v, typ: SSZType, strict: bool = False):
v_typ = type(v)
# shortcut if it's already the type we are looking for
if v_typ == typ:
return v
elif isinstance(v, int):
if isinstance(v, uint): # do not coerce from one uintX to another uintY
if issubclass(typ, uint) and v.type().byte_len == typ.byte_len:
return typ(v)
# revert to default behavior below if-else. (ValueError/bare)
else:
raise TypeError("Type %s is not an uint (or int-default uint64) type" % typ)
return typ(v)
elif isinstance(v, (list, tuple)):
return typ(*v)
elif isinstance(v, (bytes, BytesN, Bytes)):
return typ(v)
elif isinstance(v, GeneratorType):
return typ(v)
# just return as-is, Value-checkers will take care of it not being coerced, if we are not strict.
if strict and not isinstance(v, typ):
raise ValueError("Type coercion of {} to {} failed".format(v, typ))
return v
# SSZ Container base class
# -----------------------------
class Series(SSZValue):
def __iter__(self) -> Iterator[SSZValue]:
raise Exception("Not implemented")
# Note: importing ssz functionality locally, to avoid import loop
class Container(object):
class Container(Series, metaclass=SSZType):
def __init__(self, **kwargs):
cls = self.__class__
for f, t in cls.get_fields():
for f, t in cls.get_fields().items():
if f not in kwargs:
setattr(self, f, get_zero_value(t))
setattr(self, f, t.default())
else:
setattr(self, f, kwargs[f])
value = coerce_type_maybe(kwargs[f], t)
if not isinstance(value, t):
raise ValueError(f"Bad input for class {self.__class__}:"
f" field: {f} type: {t} value: {value} value type: {type(value)}")
setattr(self, f, value)
def serialize(self):
from .ssz_impl import serialize
return serialize(self, self.__class__)
return serialize(self)
def hash_tree_root(self):
from .ssz_impl import hash_tree_root
return hash_tree_root(self, self.__class__)
return hash_tree_root(self)
def signing_root(self):
from .ssz_impl import signing_root
return signing_root(self, self.__class__)
return signing_root(self)
def get_field_values(self):
cls = self.__class__
return [getattr(self, field) for field in cls.get_field_names()]
def __setattr__(self, name, value):
if name not in self.__class__.__annotations__:
raise AttributeError("Cannot change non-existing SSZ-container attribute")
field_typ = self.__class__.__annotations__[name]
value = coerce_type_maybe(value, field_typ)
if not isinstance(value, field_typ):
raise ValueError(f"Cannot set field of {self.__class__}:"
f" field: {name} type: {field_typ} value: {value} value type: {type(value)}")
super().__setattr__(name, value)
def __repr__(self):
return repr({field: getattr(self, field) for field in self.get_field_names()})
return repr({field: (getattr(self, field) if hasattr(self, field) else 'unset')
for field in self.get_fields().keys()})
def __str__(self):
output = []
for field in self.get_field_names():
output.append(f'{field}: {getattr(self, field)}')
output = [f'{self.__class__.__name__}']
for field in self.get_fields().keys():
output.append(f' {field}: {getattr(self, field)}')
return "\n".join(output)
def __eq__(self, other):
@ -139,385 +186,324 @@ class Container(object):
def __hash__(self):
return hash(self.hash_tree_root())
def copy(self):
return copy.deepcopy(self)
@classmethod
def get_fields_dict(cls):
def get_fields(cls) -> Dict[str, SSZType]:
if not hasattr(cls, '__annotations__'): # no container fields
return {}
return dict(cls.__annotations__)
@classmethod
def get_fields(cls):
return list(dict(cls.__annotations__).items())
def get_typed_values(self):
return list(zip(self.get_field_values(), self.get_field_types()))
def default(cls):
return cls(**{f: t.default() for f, t in cls.get_fields().items()})
@classmethod
def get_field_names(cls):
return list(cls.__annotations__.keys())
def is_fixed_size(cls):
return all(t.is_fixed_size() for t in cls.get_fields().values())
@classmethod
def get_field_types(cls):
# values of annotations are the types corresponding to the fields, not instance values.
return list(cls.__annotations__.values())
def __iter__(self) -> Iterator[SSZValue]:
return iter([getattr(self, field) for field in self.get_fields().keys()])
# SSZ vector
# -----------------------------
class ParamsBase(Series):
_has_params = False
def __new__(cls, *args, **kwargs):
if not cls._has_params:
raise Exception("cannot init bare type without params")
return super().__new__(cls, **kwargs)
def _is_vector_instance_of(a, b):
# Other must not be a BytesN
if issubclass(b, bytes):
return False
elif not hasattr(b, 'elem_type') or not hasattr(b, 'length'):
# Vector (b) is not an instance of Vector[X, Y] (a)
return False
elif not hasattr(a, 'elem_type') or not hasattr(a, 'length'):
# Vector[X, Y] (b) is an instance of Vector (a)
return True
else:
# Vector[X, Y] (a) is an instance of Vector[X, Y] (b)
return a.elem_type == b.elem_type and a.length == b.length
class ParamsMeta(SSZType):
def _is_equal_vector_type(a, b):
# Other must not be a BytesN
if issubclass(b, bytes):
return False
elif not hasattr(a, 'elem_type') or not hasattr(a, 'length'):
if not hasattr(b, 'elem_type') or not hasattr(b, 'length'):
# Vector == Vector
return True
else:
# Vector != Vector[X, Y]
return False
elif not hasattr(b, 'elem_type') or not hasattr(b, 'length'):
# Vector[X, Y] != Vector
return False
else:
# Vector[X, Y] == Vector[X, Y]
return a.elem_type == b.elem_type and a.length == b.length
class VectorMeta(type):
def __new__(cls, class_name, parents, attrs):
out = type.__new__(cls, class_name, parents, attrs)
if 'elem_type' in attrs and 'length' in attrs:
setattr(out, 'elem_type', attrs['elem_type'])
setattr(out, 'length', attrs['length'])
if hasattr(out, "_has_params") and getattr(out, "_has_params"):
for k, v in attrs.items():
setattr(out, k, v)
return out
def __getitem__(self, params):
if not isinstance(params, tuple) or len(params) != 2:
raise Exception("Vector must be instantiated with two args: elem type and length")
o = self.__class__(self.__name__, (Vector,), {'elem_type': params[0], 'length': params[1]})
o._name = 'Vector'
o = self.__class__(self.__name__, (self,), self.attr_from_params(params))
return o
def __subclasscheck__(self, sub):
return _is_vector_instance_of(self, sub)
def __instancecheck__(self, other):
return _is_vector_instance_of(self, other.__class__)
def __eq__(self, other):
return _is_equal_vector_type(self, other)
def __ne__(self, other):
return not _is_equal_vector_type(self, other)
def __hash__(self):
return hash(self.__class__)
class Vector(metaclass=VectorMeta):
def __init__(self, *args: Iterable):
cls = self.__class__
if not hasattr(cls, 'elem_type'):
raise TypeError("Type Vector without elem_type data cannot be instantiated")
elif not hasattr(cls, 'length'):
raise TypeError("Type Vector without length data cannot be instantiated")
if len(args) != cls.length:
if len(args) == 0:
args = [get_zero_value(cls.elem_type) for _ in range(cls.length)]
else:
raise TypeError("Typed vector with length %d cannot hold %d items" % (cls.length, len(args)))
self.items = list(args)
# cannot check non-type objects, or parametrized types
if isinstance(cls.elem_type, type) and not hasattr(cls.elem_type, '__args__'):
for i, item in enumerate(self.items):
if not issubclass(type(item), cls.elem_type):
raise TypeError("Typed vector cannot hold differently typed value"
" at index %d. Got type: %s, expected type: %s" % (i, type(item), cls.elem_type))
def serialize(self):
from .ssz_impl import serialize
return serialize(self, self.__class__)
def hash_tree_root(self):
from .ssz_impl import hash_tree_root
return hash_tree_root(self, self.__class__)
def __str__(self):
return f"{self.__name__}~{self.__class__.__name__}"
def __repr__(self):
return repr({'length': self.__class__.length, 'items': self.items})
return f"{self.__name__}~{self.__class__.__name__}"
def __getitem__(self, key):
return self.items[key]
def attr_from_params(self, p):
# single key params are valid too. Wrap them in a tuple.
params = p if isinstance(p, tuple) else (p,)
res = {'_has_params': True}
i = 0
for (name, typ) in self.__annotations__.items():
if hasattr(self.__class__, name):
res[name] = getattr(self.__class__, name)
else:
if i >= len(params):
i += 1
continue
param = params[i]
if not isinstance(param, typ):
raise TypeError(
"cannot create parametrized class with param {} as {} of type {}".format(param, name, typ))
res[name] = param
i += 1
if len(params) != i:
raise TypeError("provided parameters {} mismatch required parameter count {}".format(params, i))
return res
def __setitem__(self, key, value):
self.items[key] = value
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __eq__(self, other):
return self.hash_tree_root() == other.hash_tree_root()
# SSZ BytesN
# -----------------------------
def _is_bytes_n_instance_of(a, b):
# Other has to be a Bytes derivative class to be a BytesN
if not issubclass(b, bytes):
return False
elif not hasattr(b, 'length'):
# BytesN (b) is not an instance of BytesN[X] (a)
return False
elif not hasattr(a, 'length'):
# BytesN[X] (b) is an instance of BytesN (a)
def __subclasscheck__(self, subclass):
# check regular class system if we can, solves a lot of the normal cases.
if super().__subclasscheck__(subclass):
return True
else:
# BytesN[X] (a) is an instance of BytesN[X] (b)
return a.length == b.length
def _is_equal_bytes_n_type(a, b):
# Other has to be a Bytes derivative class to be a BytesN
if not issubclass(b, bytes):
# if they are not normal subclasses, they are of the same class.
# then they should have the same name
if subclass.__name__ != self.__name__:
return False
# If they do have the same name, they should also have the same params.
for name, typ in self.__annotations__.items():
if hasattr(self, name) and hasattr(subclass, name) \
and getattr(subclass, name) != getattr(self, name):
return False
elif not hasattr(a, 'length'):
if not hasattr(b, 'length'):
# BytesN == BytesN
return True
def __instancecheck__(self, obj):
return self.__subclasscheck__(obj.__class__)
class ElementsType(ParamsMeta):
elem_type: SSZType
length: int
class Elements(ParamsBase, metaclass=ElementsType):
pass
class BaseList(list, Elements):
def __init__(self, *args):
items = self.extract_args(*args)
if not self.value_check(items):
raise ValueError(f"Bad input for class {self.__class__}: {items}")
super().__init__(items)
@classmethod
def value_check(cls, value):
return all(isinstance(v, cls.elem_type) for v in value) and len(value) <= cls.length
@classmethod
def extract_args(cls, *args):
x = list(args)
if len(x) == 1 and isinstance(x[0], (GeneratorType, list, tuple)):
x = list(x[0])
x = [coerce_type_maybe(v, cls.elem_type) for v in x]
return x
def __str__(self):
cls = self.__class__
return f"{cls.__name__}[{cls.elem_type.__name__}, {cls.length}]({', '.join(str(v) for v in self)})"
def __repr__(self):
cls = self.__class__
return f"{cls.__name__}[{cls.elem_type.__name__}, {cls.length}]({', '.join(str(v) for v in self)})"
def __getitem__(self, k) -> SSZValue:
if isinstance(k, int): # check if we are just doing a lookup, and not slicing
if k < 0:
raise IndexError(f"cannot get item in type {self.__class__} at negative index {k}")
if k > len(self):
raise IndexError(f"cannot get item in type {self.__class__}"
f" at out of bounds index {k}")
return super().__getitem__(k)
def __setitem__(self, k, v):
if type(k) == slice:
if (k.start is not None and k.start < 0) or (k.stop is not None and k.stop > len(self)):
raise IndexError(f"cannot set item in type {self.__class__}"
f" at out of bounds slice {k} (to {v}, bound: {len(self)})")
super().__setitem__(k, [coerce_type_maybe(x, self.__class__.elem_type) for x in v])
else:
# BytesN != BytesN[X]
if k < 0:
raise IndexError(f"cannot set item in type {self.__class__} at negative index {k} (to {v})")
if k > len(self):
raise IndexError(f"cannot set item in type {self.__class__}"
f" at out of bounds index {k} (to {v}, bound: {len(self)})")
super().__setitem__(k, coerce_type_maybe(v, self.__class__.elem_type, strict=True))
def append(self, v):
super().append(coerce_type_maybe(v, self.__class__.elem_type, strict=True))
def __iter__(self) -> Iterator[SSZValue]:
return super().__iter__()
def last(self):
# be explict about getting the last item, for the non-python readers, and negative-index safety
return self[len(self) - 1]
class BitElementsType(ElementsType):
elem_type: SSZType = boolean
length: int
class Bits(BaseList, metaclass=BitElementsType):
def as_bytes(self):
as_bytearray = [0] * ((len(self) + 7) // 8)
for i in range(len(self)):
as_bytearray[i // 8] |= int(self[i]) << (i % 8)
return bytes(as_bytearray)
class Bitlist(Bits):
@classmethod
def is_fixed_size(cls):
return False
elif not hasattr(b, 'length'):
# BytesN[X] != BytesN
@classmethod
def default(cls):
return cls()
class Bitvector(Bits):
@classmethod
def extract_args(cls, *args):
if len(args) == 0:
return cls.default()
else:
return super().extract_args(*args)
@classmethod
def value_check(cls, value):
# check length limit strictly
return len(value) == cls.length and super().value_check(value)
@classmethod
def is_fixed_size(cls):
return True
@classmethod
def default(cls):
return cls(0 for _ in range(cls.length))
class List(BaseList):
@classmethod
def default(cls):
return cls()
@classmethod
def is_fixed_size(cls):
return False
class Vector(BaseList):
@classmethod
def value_check(cls, value):
# check length limit strictly
return len(value) == cls.length and super().value_check(value)
@classmethod
def default(cls):
return cls(cls.elem_type.default() for _ in range(cls.length))
@classmethod
def is_fixed_size(cls):
return cls.elem_type.is_fixed_size()
def append(self, v):
# Deep-copy and other utils like to change the internals during work.
# Only complain if we had the right size.
if len(self) == self.__class__.length:
raise Exception("cannot modify vector length")
else:
# BytesN[X] == BytesN[X]
return a.length == b.length
super().append(v)
def pop(self, *args):
raise Exception("cannot modify vector length")
class BytesNMeta(type):
def __new__(cls, class_name, parents, attrs):
out = type.__new__(cls, class_name, parents, attrs)
if 'length' in attrs:
setattr(out, 'length', attrs['length'])
out._name = 'BytesN'
out.elem_type = byte
return out
def __getitem__(self, n):
return self.__class__(self.__name__, (BytesN,), {'length': n})
def __subclasscheck__(self, sub):
return _is_bytes_n_instance_of(self, sub)
def __instancecheck__(self, other):
return _is_bytes_n_instance_of(self, other.__class__)
def __eq__(self, other):
return _is_equal_bytes_n_type(self, other)
def __ne__(self, other):
return not _is_equal_bytes_n_type(self, other)
def __hash__(self):
return hash(self.__class__)
class BytesType(ElementsType):
elem_type: SSZType = byte
length: int
def parse_bytes(val):
if val is None:
return None
elif isinstance(val, str):
# TODO: import from eth-utils instead, and do: hexstr_if_str(to_bytes, val)
return None
elif isinstance(val, bytes):
return val
elif isinstance(val, int):
return bytes([val])
class BaseBytes(bytes, Elements, metaclass=BytesType):
def __new__(cls, *args) -> "BaseBytes":
extracted_val = cls.extract_args(*args)
if not cls.value_check(extracted_val):
raise ValueError(f"Bad input for class {cls}: {extracted_val}")
return super().__new__(cls, extracted_val)
@classmethod
def extract_args(cls, *args):
x = args
if len(x) == 1 and isinstance(x[0], (GeneratorType, bytes)):
x = x[0]
if isinstance(x, bytes): # Includes BytesLike
return x
else:
return None
return bytes(x) # E.g. GeneratorType put into bytes.
@classmethod
def value_check(cls, value):
# check type and virtual length limit
return isinstance(value, bytes) and len(value) <= cls.length
def __str__(self):
cls = self.__class__
return f"{cls.__name__}[{cls.length}]: {self.hex()}"
class BytesN(bytes, metaclass=BytesNMeta):
def __new__(cls, *args):
if not hasattr(cls, 'length'):
return
bytesval = None
if len(args) == 1:
val: Union[bytes, int, str] = args[0]
bytesval = parse_bytes(val)
elif len(args) > 1:
# TODO: each int is 1 byte, check size, create bytesval
bytesval = bytes(args)
class Bytes(BaseBytes):
if bytesval is None:
if cls.length == 0:
bytesval = b''
else:
bytesval = b'\x00' * cls.length
if len(bytesval) != cls.length:
raise TypeError("BytesN[%d] cannot be initialized with value of %d bytes" % (cls.length, len(bytesval)))
return super().__new__(cls, bytesval)
def serialize(self):
from .ssz_impl import serialize
return serialize(self, self.__class__)
def hash_tree_root(self):
from .ssz_impl import hash_tree_root
return hash_tree_root(self, self.__class__)
# SSZ Defaults
# -----------------------------
def get_zero_value(typ):
if is_uint_type(typ):
return 0
elif is_list_type(typ):
return []
elif is_bool_type(typ):
return False
elif is_vector_type(typ):
return typ()
elif is_bytesn_type(typ):
return typ()
elif is_bytes_type(typ):
@classmethod
def default(cls):
return b''
elif is_container_type(typ):
return typ(**{f: get_zero_value(t) for f, t in typ.get_fields()})
@classmethod
def is_fixed_size(cls):
return False
class BytesN(BaseBytes):
@classmethod
def extract_args(cls, *args):
if len(args) == 0:
return cls.default()
else:
raise Exception("Type not supported: {}".format(typ))
return super().extract_args(*args)
@classmethod
def default(cls):
return b'\x00' * cls.length
@classmethod
def value_check(cls, value):
# check length limit strictly
return len(value) == cls.length and super().value_check(value)
@classmethod
def is_fixed_size(cls):
return True
# Type helpers
# -----------------------------
def infer_type(obj):
if is_uint_type(obj.__class__):
return obj.__class__
elif isinstance(obj, int):
return uint64
elif isinstance(obj, list):
return List[infer_type(obj[0])]
elif isinstance(obj, (Vector, Container, bool, BytesN, bytes)):
return obj.__class__
else:
raise Exception("Unknown type for {}".format(obj))
def infer_input_type(fn):
"""
Decorator to run infer_type on the obj if typ argument is None
"""
def infer_helper(obj, typ=None, **kwargs):
if typ is None:
typ = infer_type(obj)
return fn(obj, typ=typ, **kwargs)
return infer_helper
def is_bool_type(typ):
"""
Check if the given type is a bool.
"""
if hasattr(typ, '__supertype__'):
typ = typ.__supertype__
return isinstance(typ, type) and issubclass(typ, bool)
def is_list_type(typ):
"""
Check if the given type is a list.
"""
return get_origin(typ) is List or get_origin(typ) is list
def is_bytes_type(typ):
"""
Check if the given type is a ``bytes``.
"""
# Do not accept subclasses of bytes here, to avoid confusion with BytesN
return typ == bytes
def is_bytesn_type(typ):
"""
Check if the given type is a BytesN.
"""
return isinstance(typ, type) and issubclass(typ, BytesN)
def is_list_kind(typ):
"""
Check if the given type is a kind of list. Can be bytes.
"""
return is_list_type(typ) or is_bytes_type(typ)
def is_vector_type(typ):
"""
Check if the given type is a vector.
"""
return isinstance(typ, type) and issubclass(typ, Vector)
def is_vector_kind(typ):
"""
Check if the given type is a kind of vector. Can be BytesN.
"""
return is_vector_type(typ) or is_bytesn_type(typ)
def is_container_type(typ):
"""
Check if the given type is a container.
"""
return isinstance(typ, type) and issubclass(typ, Container)
T = TypeVar('T')
L = TypeVar('L')
def read_list_elem_type(list_typ: Type[List[T]]) -> T:
if list_typ.__args__ is None or len(list_typ.__args__) != 1:
raise TypeError("Supplied list-type is invalid, no element type found.")
return list_typ.__args__[0]
def read_vector_elem_type(vector_typ: Type[Vector[T, L]]) -> T:
return vector_typ.elem_type
def read_elem_type(typ):
if typ == bytes or (isinstance(typ, type) and issubclass(typ, bytes)): # bytes or bytesN
return byte
elif is_list_type(typ):
return read_list_elem_type(typ)
elif is_vector_type(typ):
return read_vector_elem_type(typ)
else:
raise TypeError("Unexpected type: {}".format(typ))
# Helpers for common BytesN types
Bytes1: BytesType = BytesN[1]
Bytes4: BytesType = BytesN[4]
Bytes8: BytesType = BytesN[8]
Bytes32: BytesType = BytesN[32]
Bytes48: BytesType = BytesN[48]
Bytes96: BytesType = BytesN[96]

View File

@ -0,0 +1,264 @@
from typing import Iterable
from .ssz_impl import serialize, hash_tree_root
from .ssz_typing import (
bit, boolean, Container, List, Vector, Bytes, BytesN,
Bitlist, Bitvector,
uint8, uint16, uint32, uint64, uint256, byte
)
from ..hash_function import hash as bytes_hash
import pytest
class EmptyTestStruct(Container):
pass
class SingleFieldTestStruct(Container):
A: byte
class SmallTestStruct(Container):
A: uint16
B: uint16
class FixedTestStruct(Container):
A: uint8
B: uint64
C: uint32
class VarTestStruct(Container):
A: uint16
B: List[uint16, 1024]
C: uint8
class ComplexTestStruct(Container):
A: uint16
B: List[uint16, 128]
C: uint8
D: Bytes[256]
E: VarTestStruct
F: Vector[FixedTestStruct, 4]
G: Vector[VarTestStruct, 2]
sig_test_data = [0 for i in range(96)]
for k, v in {0: 1, 32: 2, 64: 3, 95: 0xff}.items():
sig_test_data[k] = v
def chunk(hex: str) -> str:
return (hex + ("00" * 32))[:64] # just pad on the right, to 32 bytes (64 hex chars)
def h(a: str, b: str) -> str:
return bytes_hash(bytes.fromhex(a) + bytes.fromhex(b)).hex()
# zero hashes, as strings, for
zero_hashes = [chunk("")]
for layer in range(1, 32):
zero_hashes.append(h(zero_hashes[layer - 1], zero_hashes[layer - 1]))
def merge(a: str, branch: Iterable[str]) -> str:
"""
Merge (out on left, branch on right) leaf a with branch items, branch is from bottom to top.
"""
out = a
for b in branch:
out = h(out, b)
return out
test_data = [
("bit F", bit(False), "00", chunk("00")),
("bit T", bit(True), "01", chunk("01")),
("boolean F", boolean(False), "00", chunk("00")),
("boolean T", boolean(True), "01", chunk("01")),
("bitvector TTFTFTFF", Bitvector[8](1, 1, 0, 1, 0, 1, 0, 0), "2b", chunk("2b")),
("bitlist TTFTFTFF", Bitlist[8](1, 1, 0, 1, 0, 1, 0, 0), "2b01", h(chunk("2b"), chunk("08"))),
("bitvector FTFT", Bitvector[4](0, 1, 0, 1), "0a", chunk("0a")),
("bitlist FTFT", Bitlist[4](0, 1, 0, 1), "1a", h(chunk("0a"), chunk("04"))),
("bitvector FTF", Bitvector[3](0, 1, 0), "02", chunk("02")),
("bitlist FTF", Bitlist[3](0, 1, 0), "0a", h(chunk("02"), chunk("03"))),
("bitvector TFTFFFTTFT", Bitvector[10](1, 0, 1, 0, 0, 0, 1, 1, 0, 1), "c502", chunk("c502")),
("bitlist TFTFFFTTFT", Bitlist[16](1, 0, 1, 0, 0, 0, 1, 1, 0, 1), "c506", h(chunk("c502"), chunk("0A"))),
("bitvector TFTFFFTTFTFFFFTT", Bitvector[16](1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1),
"c5c2", chunk("c5c2")),
("bitlist TFTFFFTTFTFFFFTT", Bitlist[16](1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1),
"c5c201", h(chunk("c5c2"), chunk("10"))),
("long bitvector", Bitvector[512](1 for i in range(512)),
"ff" * 64, h("ff" * 32, "ff" * 32)),
("long bitlist", Bitlist[512](1),
"03", h(h(chunk("01"), chunk("")), chunk("01"))),
("long bitlist", Bitlist[512](1 for i in range(512)),
"ff" * 64 + "01", h(h("ff" * 32, "ff" * 32), chunk("0002"))),
("odd bitvector", Bitvector[513](1 for i in range(513)),
"ff" * 64 + "01", h(h("ff" * 32, "ff" * 32), h(chunk("01"), chunk("")))),
("odd bitlist", Bitlist[513](1 for i in range(513)),
"ff" * 64 + "03", h(h(h("ff" * 32, "ff" * 32), h(chunk("01"), chunk(""))), chunk("0102"))),
("uint8 00", uint8(0x00), "00", chunk("00")),
("uint8 01", uint8(0x01), "01", chunk("01")),
("uint8 ab", uint8(0xab), "ab", chunk("ab")),
("byte 00", byte(0x00), "00", chunk("00")),
("byte 01", byte(0x01), "01", chunk("01")),
("byte ab", byte(0xab), "ab", chunk("ab")),
("uint16 0000", uint16(0x0000), "0000", chunk("0000")),
("uint16 abcd", uint16(0xabcd), "cdab", chunk("cdab")),
("uint32 00000000", uint32(0x00000000), "00000000", chunk("00000000")),
("uint32 01234567", uint32(0x01234567), "67452301", chunk("67452301")),
("small (4567, 0123)", SmallTestStruct(A=0x4567, B=0x0123), "67452301", h(chunk("6745"), chunk("2301"))),
("small [4567, 0123]::2", Vector[uint16, 2](uint16(0x4567), uint16(0x0123)), "67452301", chunk("67452301")),
("uint32 01234567", uint32(0x01234567), "67452301", chunk("67452301")),
("uint64 0000000000000000", uint64(0x00000000), "0000000000000000", chunk("0000000000000000")),
("uint64 0123456789abcdef", uint64(0x0123456789abcdef), "efcdab8967452301", chunk("efcdab8967452301")),
("sig", BytesN[96](*sig_test_data),
"0100000000000000000000000000000000000000000000000000000000000000"
"0200000000000000000000000000000000000000000000000000000000000000"
"03000000000000000000000000000000000000000000000000000000000000ff",
h(h(chunk("01"), chunk("02")),
h("03000000000000000000000000000000000000000000000000000000000000ff", chunk("")))),
("emptyTestStruct", EmptyTestStruct(), "", chunk("")),
("singleFieldTestStruct", SingleFieldTestStruct(A=0xab), "ab", chunk("ab")),
("uint16 list", List[uint16, 32](uint16(0xaabb), uint16(0xc0ad), uint16(0xeeff)), "bbaaadc0ffee",
h(h(chunk("bbaaadc0ffee"), chunk("")), chunk("03000000")) # max length: 32 * 2 = 64 bytes = 2 chunks
),
("uint32 list", List[uint32, 128](uint32(0xaabb), uint32(0xc0ad), uint32(0xeeff)), "bbaa0000adc00000ffee0000",
# max length: 128 * 4 = 512 bytes = 16 chunks
h(merge(chunk("bbaa0000adc00000ffee0000"), zero_hashes[0:4]), chunk("03000000"))
),
("uint256 list", List[uint256, 32](uint256(0xaabb), uint256(0xc0ad), uint256(0xeeff)),
"bbaa000000000000000000000000000000000000000000000000000000000000"
"adc0000000000000000000000000000000000000000000000000000000000000"
"ffee000000000000000000000000000000000000000000000000000000000000",
h(merge(h(h(chunk("bbaa"), chunk("adc0")), h(chunk("ffee"), chunk(""))), zero_hashes[2:5]), chunk("03000000"))
),
("uint256 list long", List[uint256, 128](i for i in range(1, 20)),
"".join([i.to_bytes(length=32, byteorder='little').hex() for i in range(1, 20)]),
h(merge(
h(
h(
h(
h(h(chunk("01"), chunk("02")), h(chunk("03"), chunk("04"))),
h(h(chunk("05"), chunk("06")), h(chunk("07"), chunk("08"))),
),
h(
h(h(chunk("09"), chunk("0a")), h(chunk("0b"), chunk("0c"))),
h(h(chunk("0d"), chunk("0e")), h(chunk("0f"), chunk("10"))),
)
),
h(
h(
h(h(chunk("11"), chunk("12")), h(chunk("13"), chunk(""))),
zero_hashes[2]
),
zero_hashes[3]
)
),
zero_hashes[5:7]), chunk("13000000")) # 128 chunks = 7 deep
),
("fixedTestStruct", FixedTestStruct(A=0xab, B=0xaabbccdd00112233, C=0x12345678), "ab33221100ddccbbaa78563412",
h(h(chunk("ab"), chunk("33221100ddccbbaa")), h(chunk("78563412"), chunk("")))),
("varTestStruct nil", VarTestStruct(A=0xabcd, C=0xff), "cdab07000000ff",
h(h(chunk("cdab"), h(zero_hashes[6], chunk("00000000"))), h(chunk("ff"), chunk("")))),
("varTestStruct empty", VarTestStruct(A=0xabcd, B=List[uint16, 1024](), C=0xff), "cdab07000000ff",
h(h(chunk("cdab"), h(zero_hashes[6], chunk("00000000"))), h(chunk("ff"), chunk("")))), # log2(1024*2/32)= 6 deep
("varTestStruct some", VarTestStruct(A=0xabcd, B=List[uint16, 1024](1, 2, 3), C=0xff),
"cdab07000000ff010002000300",
h(
h(
chunk("cdab"),
h(
merge(
chunk("010002000300"),
zero_hashes[0:6]
),
chunk("03000000") # length mix in
)
),
h(chunk("ff"), chunk(""))
)),
("complexTestStruct",
ComplexTestStruct(
A=0xaabb,
B=List[uint16, 128](0x1122, 0x3344),
C=0xff,
D=Bytes[256](b"foobar"),
E=VarTestStruct(A=0xabcd, B=List[uint16, 1024](1, 2, 3), C=0xff),
F=Vector[FixedTestStruct, 4](
FixedTestStruct(A=0xcc, B=0x4242424242424242, C=0x13371337),
FixedTestStruct(A=0xdd, B=0x3333333333333333, C=0xabcdabcd),
FixedTestStruct(A=0xee, B=0x4444444444444444, C=0x00112233),
FixedTestStruct(A=0xff, B=0x5555555555555555, C=0x44556677)),
G=Vector[VarTestStruct, 2](
VarTestStruct(A=0xdead, B=List[uint16, 1024](1, 2, 3), C=0x11),
VarTestStruct(A=0xbeef, B=List[uint16, 1024](4, 5, 6), C=0x22)),
),
"bbaa"
"47000000" # offset of B, []uint16
"ff"
"4b000000" # offset of foobar
"51000000" # offset of E
"cc424242424242424237133713"
"dd3333333333333333cdabcdab"
"ee444444444444444433221100"
"ff555555555555555577665544"
"5e000000" # pointer to G
"22114433" # contents of B
"666f6f626172" # foobar
"cdab07000000ff010002000300" # contents of E
"08000000" "15000000" # [start G]: local offsets of [2]varTestStruct
"adde0700000011010002000300"
"efbe0700000022040005000600",
h(
h(
h( # A and B
chunk("bbaa"),
h(merge(chunk("22114433"), zero_hashes[0:3]), chunk("02000000")) # 2*128/32 = 8 chunks
),
h( # C and D
chunk("ff"),
h(merge(chunk("666f6f626172"), zero_hashes[0:3]), chunk("06000000")) # 256/32 = 8 chunks
)
),
h(
h( # E and F
h(h(chunk("cdab"), h(merge(chunk("010002000300"), zero_hashes[0:6]), chunk("03000000"))),
h(chunk("ff"), chunk(""))),
h(
h(
h(h(chunk("cc"), chunk("4242424242424242")), h(chunk("37133713"), chunk(""))),
h(h(chunk("dd"), chunk("3333333333333333")), h(chunk("cdabcdab"), chunk(""))),
),
h(
h(h(chunk("ee"), chunk("4444444444444444")), h(chunk("33221100"), chunk(""))),
h(h(chunk("ff"), chunk("5555555555555555")), h(chunk("77665544"), chunk(""))),
),
)
),
h( # G and padding
h(
h(h(chunk("adde"), h(merge(chunk("010002000300"), zero_hashes[0:6]), chunk("03000000"))),
h(chunk("11"), chunk(""))),
h(h(chunk("efbe"), h(merge(chunk("040005000600"), zero_hashes[0:6]), chunk("03000000"))),
h(chunk("22"), chunk(""))),
),
chunk("")
)
)
))
]
@pytest.mark.parametrize("name, value, serialized, _", test_data)
def test_serialize(name, value, serialized, _):
assert serialize(value) == bytes.fromhex(serialized)
@pytest.mark.parametrize("name, value, _, root", test_data)
def test_hash_tree_root(name, value, _, root):
assert hash_tree_root(value) == bytes.fromhex(root)

View File

@ -0,0 +1,233 @@
from .ssz_typing import (
SSZValue, SSZType, BasicValue, BasicType, Series, ElementsType,
Elements, bit, boolean, Container, List, Vector, Bytes, BytesN,
byte, uint, uint8, uint16, uint32, uint64, uint128, uint256,
Bytes32, Bytes48
)
def expect_value_error(fn, msg):
try:
fn()
raise AssertionError(msg)
except ValueError:
pass
def test_subclasses():
for u in [uint, uint8, uint16, uint32, uint64, uint128, uint256]:
assert issubclass(u, uint)
assert issubclass(u, int)
assert issubclass(u, BasicValue)
assert issubclass(u, SSZValue)
assert isinstance(u, SSZType)
assert isinstance(u, BasicType)
assert issubclass(boolean, BasicValue)
assert isinstance(boolean, BasicType)
for c in [Container, List, Vector, Bytes, BytesN]:
assert issubclass(c, Series)
assert issubclass(c, SSZValue)
assert isinstance(c, SSZType)
assert not issubclass(c, BasicValue)
assert not isinstance(c, BasicType)
for c in [List, Vector, Bytes, BytesN]:
assert issubclass(c, Elements)
assert isinstance(c, ElementsType)
def test_basic_instances():
for u in [uint, uint8, byte, uint16, uint32, uint64, uint128, uint256]:
v = u(123)
assert isinstance(v, uint)
assert isinstance(v, int)
assert isinstance(v, BasicValue)
assert isinstance(v, SSZValue)
assert isinstance(boolean(True), BasicValue)
assert isinstance(boolean(False), BasicValue)
assert isinstance(bit(True), boolean)
assert isinstance(bit(False), boolean)
def test_basic_value_bounds():
max = {
boolean: 2 ** 1,
bit: 2 ** 1,
uint8: 2 ** (8 * 1),
byte: 2 ** (8 * 1),
uint16: 2 ** (8 * 2),
uint32: 2 ** (8 * 4),
uint64: 2 ** (8 * 8),
uint128: 2 ** (8 * 16),
uint256: 2 ** (8 * 32),
}
for k, v in max.items():
# this should work
assert k(v - 1) == v - 1
# but we do not allow overflows
expect_value_error(lambda: k(v), "no overflows allowed")
for k, _ in max.items():
# this should work
assert k(0) == 0
# but we do not allow underflows
expect_value_error(lambda: k(-1), "no underflows allowed")
def test_container():
class Foo(Container):
a: uint8
b: uint32
empty = Foo()
assert empty.a == uint8(0)
assert empty.b == uint32(0)
assert issubclass(Foo, Container)
assert issubclass(Foo, SSZValue)
assert issubclass(Foo, Series)
assert Foo.is_fixed_size()
x = Foo(a=uint8(123), b=uint32(45))
assert x.a == 123
assert x.b == 45
assert isinstance(x.a, uint8)
assert isinstance(x.b, uint32)
assert x.type().is_fixed_size()
class Bar(Container):
a: uint8
b: List[uint8, 1024]
assert not Bar.is_fixed_size()
y = Bar(a=123, b=List[uint8, 1024](uint8(1), uint8(2)))
assert y.a == 123
assert isinstance(y.a, uint8)
assert len(y.b) == 2
assert isinstance(y.a, uint8)
assert isinstance(y.b, List[uint8, 1024])
assert not y.type().is_fixed_size()
assert y.b[0] == 1
v: List = y.b
assert v.type().elem_type == uint8
assert v.type().length == 1024
y.a = 42
try:
y.a = 256 # out of bounds
assert False
except ValueError:
pass
try:
y.a = uint16(255) # within bounds, wrong type
assert False
except ValueError:
pass
try:
y.not_here = 5
assert False
except AttributeError:
pass
def test_list():
typ = List[uint64, 128]
assert issubclass(typ, List)
assert issubclass(typ, SSZValue)
assert issubclass(typ, Series)
assert issubclass(typ, Elements)
assert isinstance(typ, ElementsType)
assert not typ.is_fixed_size()
assert len(typ()) == 0 # empty
assert len(typ(uint64(0))) == 1 # single arg
assert len(typ(uint64(i) for i in range(10))) == 10 # generator
assert len(typ(uint64(0), uint64(1), uint64(2))) == 3 # args
assert isinstance(typ(1, 2, 3, 4, 5)[4], uint64) # coercion
assert isinstance(typ(i for i in range(10))[9], uint64) # coercion in generator
v = typ(uint64(0))
v[0] = uint64(123)
assert v[0] == 123
assert isinstance(v[0], uint64)
assert isinstance(v, List)
assert isinstance(v, List[uint64, 128])
assert isinstance(v, typ)
assert isinstance(v, SSZValue)
assert isinstance(v, Series)
assert issubclass(v.type(), Elements)
assert isinstance(v.type(), ElementsType)
assert len(typ([i for i in range(10)])) == 10 # cast py list to SSZ list
foo = List[uint32, 128](0 for i in range(128))
foo[0] = 123
foo[1] = 654
foo[127] = 222
assert sum(foo) == 999
try:
foo[3] = 2 ** 32 # out of bounds
except ValueError:
pass
try:
foo[3] = uint64(2 ** 32 - 1) # within bounds, wrong type
assert False
except ValueError:
pass
try:
foo[128] = 100
assert False
except IndexError:
pass
try:
foo[-1] = 100 # valid in normal python lists
assert False
except IndexError:
pass
try:
foo[128] = 100 # out of bounds
assert False
except IndexError:
pass
def test_bytesn_subclass():
assert isinstance(BytesN[32](b'\xab' * 32), Bytes32)
assert not isinstance(BytesN[32](b'\xab' * 32), Bytes48)
assert issubclass(BytesN[32](b'\xab' * 32).type(), Bytes32)
assert issubclass(BytesN[32], Bytes32)
class Hash(Bytes32):
pass
assert isinstance(Hash(b'\xab' * 32), Bytes32)
assert not isinstance(Hash(b'\xab' * 32), Bytes48)
assert issubclass(Hash(b'\xab' * 32).type(), Bytes32)
assert issubclass(Hash, Bytes32)
assert not issubclass(Bytes48, Bytes32)
assert len(Bytes32() + Bytes48()) == 80
def test_uint_math():
assert uint8(0) + uint8(uint32(16)) == uint8(16) # allow explict casting to make invalid addition valid
expect_value_error(lambda: uint8(0) - uint8(1), "no underflows allowed")
expect_value_error(lambda: uint8(1) + uint8(255), "no overflows allowed")
expect_value_error(lambda: uint8(0) + 256, "no overflows allowed")
expect_value_error(lambda: uint8(42) + uint32(123), "no mixed types")
expect_value_error(lambda: uint32(42) + uint8(123), "no mixed types")
assert type(uint32(1234) + 56) == uint32

View File

@ -0,0 +1,80 @@
import pytest
from .merkle_minimal import zerohashes, merkleize_chunks, get_merkle_root
from .hash_function import hash
def h(a: bytes, b: bytes) -> bytes:
return hash(a + b)
def e(v: int) -> bytes:
# prefix with 0xfff... to make it non-zero
return b'\xff' * 28 + v.to_bytes(length=4, byteorder='little')
def z(i: int) -> bytes:
return zerohashes[i]
cases = [
# limit 0: always zero hash
(0, 0, z(0)),
(1, 0, None), # cut-off due to limit
(2, 0, None), # cut-off due to limit
# limit 1: padded to 1 element if not already. Returned (like identity func)
(0, 1, z(0)),
(1, 1, e(0)),
(2, 1, None), # cut-off due to limit
(1, 1, e(0)),
(0, 2, h(z(0), z(0))),
(1, 2, h(e(0), z(0))),
(2, 2, h(e(0), e(1))),
(3, 2, None), # cut-off due to limit
(16, 2, None), # bigger cut-off due to limit
(0, 4, h(h(z(0), z(0)), z(1))),
(1, 4, h(h(e(0), z(0)), z(1))),
(2, 4, h(h(e(0), e(1)), z(1))),
(3, 4, h(h(e(0), e(1)), h(e(2), z(0)))),
(4, 4, h(h(e(0), e(1)), h(e(2), e(3)))),
(5, 4, None), # cut-off due to limit
(0, 8, h(h(h(z(0), z(0)), z(1)), z(2))),
(1, 8, h(h(h(e(0), z(0)), z(1)), z(2))),
(2, 8, h(h(h(e(0), e(1)), z(1)), z(2))),
(3, 8, h(h(h(e(0), e(1)), h(e(2), z(0))), z(2))),
(4, 8, h(h(h(e(0), e(1)), h(e(2), e(3))), z(2))),
(5, 8, h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), z(0)), z(1)))),
(6, 8, h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(z(0), z(0))))),
(7, 8, h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), z(0))))),
(8, 8, h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7))))),
(9, 8, None), # cut-off due to limit
(0, 16, h(h(h(h(z(0), z(0)), z(1)), z(2)), z(3))),
(1, 16, h(h(h(h(e(0), z(0)), z(1)), z(2)), z(3))),
(2, 16, h(h(h(h(e(0), e(1)), z(1)), z(2)), z(3))),
(3, 16, h(h(h(h(e(0), e(1)), h(e(2), z(0))), z(2)), z(3))),
(4, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), z(2)), z(3))),
(5, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), z(0)), z(1))), z(3))),
(6, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(z(0), z(0)))), z(3))),
(7, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), z(0)))), z(3))),
(8, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), z(3))),
(9, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), h(h(h(e(8), z(0)), z(1)), z(2)))),
]
@pytest.mark.parametrize(
'count,limit,value',
cases,
)
def test_merkleize_chunks_and_get_merkle_root(count, limit, value):
chunks = [e(i) for i in range(count)]
if value is None:
bad = False
try:
merkleize_chunks(chunks, limit=limit)
bad = True
except AssertionError:
pass
if bad:
assert False, "expected merkleization to be invalid"
else:
assert merkleize_chunks(chunks, limit=limit) == value
assert get_merkle_root(chunks, pad_to=limit) == value

View File

@ -1,4 +1,7 @@
-r requirements.txt
pytest>=3.6,<3.7
pytest>=4.4
../config_helpers
flake8==3.7.7
mypy==0.701
pytest-cov
pytest-xdist

View File

@ -1,5 +1,6 @@
eth-utils>=1.3.0,<2
eth-typing>=2.1.0,<3.0.0
pycryptodome==3.7.3
py_ecc>=1.6.0
typing_inspect==0.4.0
py_ecc==1.7.1
dataclasses==0.6
ssz==0.1.0a10

View File

@ -8,7 +8,8 @@ setup(
"eth-utils>=1.3.0,<2",
"eth-typing>=2.1.0,<3.0.0",
"pycryptodome==3.7.3",
"py_ecc>=1.6.0",
"typing_inspect==0.4.0"
"py_ecc==1.7.1",
"ssz==0.1.0a10",
"dataclasses==0.6",
]
)