Merge pull request #2057 from ethereum/dev

release v0.12.3
This commit is contained in:
Danny Ryan 2020-09-18 09:32:42 -06:00 committed by GitHub
commit 7748c70c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 1923 additions and 1026 deletions

View File

@ -44,32 +44,19 @@ commands:
venv_name: v22-pyspec
reqs_checksum: cache-{{ checksum "setup.py" }}
venv_path: ./venv
restore_deposit_contract_compiler_cached_venv:
description: "Restore the venv from cache for the deposit contract compiler"
steps:
- restore_cached_venv:
venv_name: v23-deposit-contract-compiler
reqs_checksum: cache-{{ checksum "deposit_contract/compiler/requirements.txt" }}
save_deposit_contract_compiler_cached_venv:
description: "Save the venv to cache for later use of the deposit contract compiler"
steps:
- save_cached_venv:
venv_name: v23-deposit-contract-compiler
reqs_checksum: cache-{{ checksum "deposit_contract/compiler/requirements.txt" }}
venv_path: ./deposit_contract/compiler/venv
restore_deposit_contract_tester_cached_venv:
description: "Restore the venv from cache for the deposit contract tester"
steps:
- restore_cached_venv:
venv_name: v22-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "deposit_contract/tester/requirements.txt" }}
venv_name: v23-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "solidity_deposit_contract/web3_tester/requirements.txt" }}
save_deposit_contract_tester_cached_venv:
description: "Save the venv to cache for later use of the deposit contract tester"
steps:
- save_cached_venv:
venv_name: v22-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "deposit_contract/tester/requirements.txt" }}
venv_path: ./deposit_contract/tester/venv
venv_name: v23-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "solidity_deposit_contract/web3_tester/requirements.txt" }}
venv_path: ./solidity_deposit_contract/web3_tester/venv
jobs:
checkout_specs:
docker:
@ -143,22 +130,54 @@ jobs:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv
- run:
name: Run linter
name: Run linter for pyspec
command: make lint
install_deposit_contract_compiler:
docker:
# The deposit contract compiler is pinned to python 3.7 because of the vyper version pin.
- image: circleci/python:3.7
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_compiler_cached_venv
- run:
name: Install deposit contract compiler requirements
command: make install_deposit_contract_compiler
- save_deposit_contract_compiler_cached_venv
install_deposit_contract_tester:
name: Run linter for test generators
command: make lint_generators
build_deposit_contract:
docker:
- image: ethereum/solc:0.6.11-alpine
steps:
- checkout
- run:
name: Install build essentials
command: |
apk update
apk add git make
- run:
name: Compile the contract
command: |
make compile_deposit_contract
git diff --color --exit-code
- persist_to_workspace:
root: .
paths:
- ./solidity_deposit_contract/deposit_contract.json
- ./build/combined.json
- ./solidity_deposit_contract/lib
test_deposit_contract:
docker:
- image: nixorg/nix:circleci
steps:
- checkout
- restore_cache:
key: nix-store-test-v2
- attach_workspace:
at: /tmp/
- run:
name: Test the contract
command: |
mkdir build
cp -r /tmp/build/* build
cp -r /tmp/solidity_deposit_contract/lib/* ./solidity_deposit_contract/lib
cp -r /tmp/solidity_deposit_contract/deposit_contract.json ./solidity_deposit_contract/deposit_contract.json
nix-shell --command 'make test_deposit_contract' ./solidity_deposit_contract/shell.nix
- save_cache:
key: nix-store-test-v2
paths:
- /nix
install_deposit_contract_web3_tester:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
@ -168,20 +187,9 @@ jobs:
- restore_deposit_contract_tester_cached_venv
- run:
name: Install deposit contract tester requirements
command: make install_deposit_contract_tester
command: make install_deposit_contract_web3_tester
- save_deposit_contract_tester_cached_venv
test_compile_deposit_contract:
docker:
- image: circleci/python:3.7
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_compiler_cached_venv
- run:
name: Run deposit contract compile test
command: make test_compile_deposit_contract
test_deposit_contract:
test_deposit_contract_web3_tests:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
@ -190,9 +198,8 @@ jobs:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_tester_cached_venv
- run:
name: Run deposit contract test
command: make test_deposit_contract
name: Run deposit contract test with web3.py
command: make test_deposit_contract_web3_tests
workflows:
version: 2.1
test_spec:
@ -209,15 +216,15 @@ workflows:
- lint:
requires:
- test
- install_deposit_contract_compiler:
- install_deposit_contract_web3_tester:
requires:
- checkout_specs
- test_compile_deposit_contract:
- test_deposit_contract_web3_tests:
requires:
- install_deposit_contract_compiler
- install_deposit_contract_tester:
requires:
- checkout_specs
- install_deposit_contract_web3_tester
build_and_test_deposit_contract:
jobs:
- build_deposit_contract
- test_deposit_contract:
requires:
- install_deposit_contract_tester
- build_deposit_contract

2
.gitattributes vendored
View File

@ -1 +1 @@
*.vy linguist-language=Python
*.sol linguist-language=Solidity

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/ds-test"]
path = solidity_deposit_contract/lib/ds-test
url = https://github.com/dapphub/ds-test

View File

@ -1,11 +1,14 @@
SPEC_DIR = ./specs
SSZ_DIR = ./ssz
TEST_LIBS_DIR = ./tests/core
TEST_GENERATORS_DIR = ./tests/generators
PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec
TEST_VECTOR_DIR = ../eth2.0-spec-tests/tests
GENERATOR_DIR = ./tests/generators
DEPOSIT_CONTRACT_COMPILER_DIR = ./deposit_contract/compiler
DEPOSIT_CONTRACT_TESTER_DIR = ./deposit_contract/tester
SOLIDITY_DEPOSIT_CONTRACT_DIR = ./solidity_deposit_contract
SOLIDITY_DEPOSIT_CONTRACT_SOURCE = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/deposit_contract.sol
SOLIDITY_FILE_NAME = deposit_contract.json
DEPOSIT_CONTRACT_TESTER_DIR = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/web3_tester
CONFIGS_DIR = ./configs
# Collect a list of generator names
@ -25,6 +28,11 @@ COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html
CURRENT_DIR = ${CURDIR}
LINTER_CONFIG_FILE = $(CURRENT_DIR)/linter.ini
export DAPP_SKIP_BUILD:=1
export DAPP_SRC:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)
export DAPP_LIB:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)/lib
export DAPP_JSON:=build/combined.json
.PHONY: clean partial_clean all test citest lint generate_tests pyspec install_test open_cov \
install_deposit_contract_tester test_deposit_contract install_deposit_contract_compiler \
compile_deposit_contract test_compile_deposit_contract check_toc
@ -38,7 +46,6 @@ partial_clean:
rm -rf .pytest_cache
rm -f .coverage
rm -rf $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_COMPILER_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/phase0
rm -rf $(PY_SPEC_DIR)/phase1
@ -46,7 +53,7 @@ partial_clean:
rm -rf $(PY_SPEC_DIR)/.coverage
rm -rf $(PY_SPEC_DIR)/test-reports
rm -rf eth2spec.egg-info dist build
rm -rf build
clean: partial_clean
rm -rf venv
@ -86,7 +93,7 @@ find_test: pyspec
citest: pyspec
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
python -m pytest -n 4 --disable-bls --junitxml=eth2spec/test_results.xml eth2spec
python -m pytest -n 4 --bls-type=milagro --junitxml=eth2spec/test_results.xml eth2spec
open_cov:
((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) &
@ -107,23 +114,29 @@ lint: pyspec
flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.phase1
install_deposit_contract_tester:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt
test_deposit_contract:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); . venv/bin/activate; \
python -m pytest .
install_deposit_contract_compiler:
cd $(DEPOSIT_CONTRACT_COMPILER_DIR); python3.7 -m venv venv; . venv/bin/activate; pip3.7 install -r requirements.txt
lint_generators: pyspec
. venv/bin/activate; cd $(TEST_GENERATORS_DIR); \
flake8 --config $(LINTER_CONFIG_FILE)
compile_deposit_contract:
cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \
python3.7 deposit_contract/compile.py ../contracts/validator_registration.vy
@cd $(SOLIDITY_DEPOSIT_CONTRACT_DIR)
@git submodule update --recursive --init
@solc --metadata-literal --optimize --optimize-runs 5000000 --bin --abi --combined-json=abi,bin,bin-runtime,srcmap,srcmap-runtime,ast,metadata,storage-layout --overwrite -o build $(SOLIDITY_DEPOSIT_CONTRACT_SOURCE) $(SOLIDITY_DEPOSIT_CONTRACT_DIR)/tests/deposit_contract.t.sol
@/bin/echo -n '{"abi": ' > $(SOLIDITY_FILE_NAME)
@cat build/DepositContract.abi >> $(SOLIDITY_FILE_NAME)
@/bin/echo -n ', "bytecode": "0x' >> $(SOLIDITY_FILE_NAME)
@cat build/DepositContract.bin >> $(SOLIDITY_FILE_NAME)
@/bin/echo -n '"}' >> $(SOLIDITY_FILE_NAME)
test_compile_deposit_contract:
cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \
python3.7 -m pytest .
test_deposit_contract:
dapp test -v --fuzz-runs 5
install_deposit_contract_web3_tester:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt
test_deposit_contract_web3_tests:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); . venv/bin/activate; \
python -m pytest .
# Runs a generator, identified by param 1
define run_generator

View File

@ -1,6 +1,6 @@
# Ethereum 2.0 Specifications
[![Join the chat at https://discord.gg/hpFs23p](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/hpFs23p) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/qGpsxSA) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm).

View File

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

View File

@ -2,6 +2,7 @@
# Note: the intention of this file (for now) is to illustrate what a mainnet configuration could look like.
# Some of these constants may still change before the launch of Phase 0.
CONFIG_NAME: "mainnet"
# Misc
# ---------------------------------------------------------------
@ -27,6 +28,8 @@ HYSTERESIS_QUOTIENT: 4
HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5
# 3
PROPORTIONAL_SLASHING_MULTIPLIER: 3
# Fork Choice
@ -99,8 +102,6 @@ SLOTS_PER_HISTORICAL_ROOT: 8192
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# 2**8 (= 256) epochs ~27 hours
SHARD_COMMITTEE_PERIOD: 256
# 2**6 (= 64) epochs ~7 hours
MAX_EPOCHS_PER_CROSSLINK: 64
# 2**2 (= 4) epochs 25.6 minutes
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4

View File

@ -1,11 +1,12 @@
# Mainnet preset - phase 1
CONFIG_NAME: "mainnet"
# phase1-fork
# ---------------------------------------------------------------
PHASE_1_FORK_VERSION: 0x01000000
# [STUB]
PHASE_1_GENESIS_SLOT: 32
PHASE_1_FORK_SLOT: 0
INITIAL_ACTIVE_SHARDS: 64
@ -69,8 +70,6 @@ EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768
EPOCHS_PER_CUSTODY_PERIOD: 16384
# 2**11 (= 2,048) epochs, ~9 days
CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048
# 2**14 (= 16,384) epochs
CUSTODY_RESPONSE_DEADLINE: 16384
# 2**15 (= 32,768) epochs, ~146 days
MAX_CHUNK_CHALLENGE_DELAY: 32768

View File

@ -1,5 +1,6 @@
# Minimal preset
CONFIG_NAME: "minimal"
# Misc
# ---------------------------------------------------------------
@ -26,7 +27,8 @@ HYSTERESIS_QUOTIENT: 4
HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5
# 3
PROPORTIONAL_SLASHING_MULTIPLIER: 3
# Fork Choice
@ -99,8 +101,6 @@ SLOTS_PER_HISTORICAL_ROOT: 64
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit
SHARD_COMMITTEE_PERIOD: 64
# [customized] fast catchup crosslinks
MAX_EPOCHS_PER_CROSSLINK: 4
# 2**2 (= 4) epochs
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4

View File

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

View File

@ -1,38 +0,0 @@
# Deposit contract
## How to set up the testing environment?
Under the `eth2.0-specs` directory, execute:
```sh
make install_deposit_contract_tester
```
## How to compile the contract?
```sh
make compile_deposit_contract
```
The compiler dependencies can be installed with:
```sh
make install_deposit_contract_compiler
```
Note that this requires python 3.7 to be installed. The pinned vyper version will not work on 3.8.
The ABI and bytecode will be updated at [`contracts/validator_registration.json`](./contracts/validator_registration.json).
## How to run tests?
For running the contract tests:
```sh
make test_deposit_contract
```
For testing the compiler output against the expected formally-verified bytecode:
```sh
make test_compile_deposit_contract
```

View File

@ -1,31 +0,0 @@
import argparse
import json
import os
from vyper import compiler
DIR = os.path.dirname(__file__)
def generate_compiled_json(file_path: str):
deposit_contract_code = open(file_path).read()
abi = compiler.mk_full_signature(deposit_contract_code)
bytecode = compiler.compile_code(deposit_contract_code)['bytecode']
contract_json = {
'abi': abi,
'bytecode': bytecode,
}
# write json
basename = os.path.basename(file_path)
dirname = os.path.dirname(file_path)
contract_name = basename.split('.')[0]
with open(dirname + "/{}.json".format(contract_name), 'w') as f_write:
json.dump(contract_json, f_write)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("path", type=str, help="the path of the contract")
args = parser.parse_args()
path = args.path
generate_compiled_json(path)

View File

@ -1,29 +0,0 @@
from vyper import compiler
import json
import os
DIR = os.path.dirname(__file__)
def get_deposit_contract_code():
file_path = os.path.join(DIR, '../../contracts/validator_registration.vy')
deposit_contract_code = open(file_path).read()
return deposit_contract_code
def get_deposit_contract_json():
file_path = os.path.join(DIR, '../../contracts/validator_registration.json')
deposit_contract_json = open(file_path).read()
return json.loads(deposit_contract_json)
def test_compile_deposit_contract():
compiled_deposit_contract_json = get_deposit_contract_json()
deposit_contract_code = get_deposit_contract_code()
abi = compiler.mk_full_signature(deposit_contract_code)
bytecode = compiler.compile_code(deposit_contract_code)['bytecode']
assert abi == compiled_deposit_contract_json["abi"]
assert bytecode == compiled_deposit_contract_json["bytecode"]

View File

@ -1,7 +0,0 @@
# Vyper beta version used to generate the bytecode that was then formally verified.
# On top of this beta version, a later change was backported, and included in the formal verification:
# https://github.com/vyperlang/vyper/issues/1761
# The resulting vyper version is pinned and maintained as protected branch.
git+https://github.com/vyperlang/vyper@1761-HOTFIX-v0.1.0-beta.13
pytest==3.6.1

View File

@ -1,10 +0,0 @@
from distutils.core import setup
setup(
name='deposit_contract_compiler',
packages=['deposit_contract'],
package_dir={"": "."},
python_requires="3.7", # pinned vyper compiler stops working after 3.7. See vyper issue 1835.
tests_requires=["pytest==3.6.1"],
install_requires=[], # see requirements.txt file
)

File diff suppressed because one or more lines are too long

View File

@ -1,110 +0,0 @@
# Vyper target 0.1.0b13.hotfix1761
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32
MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
PUBKEY_LENGTH: constant(uint256) = 48 # bytes
WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
AMOUNT_LENGTH: constant(uint256) = 8 # bytes
DepositEvent: event({
pubkey: bytes[48],
withdrawal_credentials: bytes[32],
amount: bytes[8],
signature: bytes[96],
index: bytes[8],
})
branch: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
deposit_count: uint256
# Compute hashes in empty sparse Merkle tree
zero_hashes: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
@public
def __init__():
for i in range(DEPOSIT_CONTRACT_TREE_DEPTH - 1):
self.zero_hashes[i + 1] = sha256(concat(self.zero_hashes[i], self.zero_hashes[i]))
@private
@constant
def to_little_endian_64(value: uint256) -> bytes[8]:
# Reversing bytes using bitwise uint256 manipulations
# Note: array accesses of bytes[] are not currently supported in Vyper
# Note: this function is only called when `value < 2**64`
y: uint256 = 0
x: uint256 = value
for _ in range(8):
y = shift(y, 8)
y = y + bitwise_and(x, 255)
x = shift(x, -8)
return slice(convert(y, bytes32), start=24, len=8)
@public
@constant
def get_deposit_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`
node = sha256(concat(self.branch[height], node))
else:
node = sha256(concat(node, self.zero_hashes[height]))
size /= 2
return sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
@public
@constant
def get_deposit_count() -> bytes[8]:
return self.to_little_endian_64(self.deposit_count)
@payable
@public
def deposit(pubkey: bytes[PUBKEY_LENGTH],
withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH],
signature: bytes[SIGNATURE_LENGTH],
deposit_data_root: bytes32):
# Avoid overflowing the Merkle tree (and prevent edge case in computing `self.branch`)
assert self.deposit_count < MAX_DEPOSIT_COUNT
# Check deposit amount
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
# Length checks for safety
assert len(pubkey) == PUBKEY_LENGTH
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
assert len(signature) == SIGNATURE_LENGTH
# Emit `DepositEvent` log
amount: bytes[8] = self.to_little_endian_64(deposit_amount)
log.DepositEvent(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
# Compute deposit data root (`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(
sha256(slice(signature, start=0, len=64)),
sha256(concat(slice(signature, start=64, len=SIGNATURE_LENGTH - 64), zero_bytes32)),
))
node: bytes32 = sha256(concat(
sha256(concat(pubkey_root, withdrawal_credentials)),
sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
))
# Verify computed and expected deposit data roots match
assert node == deposit_data_root
# Add deposit data 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):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
self.branch[height] = node
break
node = sha256(concat(self.branch[height], node))
size /= 2

View File

@ -1,3 +1,4 @@
from enum import Enum, auto
from setuptools import setup, find_packages, Command
from setuptools.command.build_py import build_py
from distutils import dir_util
@ -14,6 +15,13 @@ class SpecObject(NamedTuple):
custom_types: Dict[str, str]
constants: Dict[str, str]
ssz_objects: Dict[str, str]
dataclasses: Dict[str, str]
class CodeBlockType(Enum):
SSZ = auto()
DATACLASS = auto()
FUNCTION = auto()
def get_spec(file_name: str) -> SpecObject:
@ -28,8 +36,9 @@ def get_spec(file_name: str) -> SpecObject:
functions: Dict[str, str] = {}
constants: Dict[str, str] = {}
ssz_objects: Dict[str, str] = {}
dataclasses: Dict[str, str] = {}
function_matcher = re.compile(FUNCTION_REGEX)
is_ssz = False
block_type = CodeBlockType.FUNCTION
custom_types: Dict[str, str] = {}
for linenum, line in enumerate(open(file_name).readlines()):
line = line.rstrip()
@ -43,20 +52,26 @@ def get_spec(file_name: str) -> SpecObject:
else:
# Handle function definitions & ssz_objects
if pulling_from is not None:
# SSZ Object
if len(line) > 18 and line[:6] == 'class ' and line[-12:] == '(Container):':
name = line[6:-12]
# Check consistency with markdown header
assert name == current_name
is_ssz = True
# function definition
block_type = CodeBlockType.SSZ
elif line[:10] == '@dataclass':
block_type = CodeBlockType.DATACLASS
elif function_matcher.match(line) is not None:
current_name = function_matcher.match(line).group(0)
is_ssz = False
if is_ssz:
block_type = CodeBlockType.FUNCTION
if block_type == CodeBlockType.SSZ:
ssz_objects[current_name] = ssz_objects.get(current_name, '') + line + '\n'
else:
elif block_type == CodeBlockType.DATACLASS:
dataclasses[current_name] = dataclasses.get(current_name, '') + line + '\n'
elif block_type == CodeBlockType.FUNCTION:
functions[current_name] = functions.get(current_name, '') + line + '\n'
else:
pass
# Handle constant and custom types table entries
elif pulling_from is None and len(line) > 0 and line[0] == '|':
row = line[1:].split('|')
@ -75,7 +90,7 @@ def get_spec(file_name: str) -> SpecObject:
constants[row[0]] = row[1].replace('**TBD**', '2**32')
elif row[1].startswith('uint') or row[1].startswith('Bytes'):
custom_types[row[0]] = row[1]
return SpecObject(functions, custom_types, constants, ssz_objects)
return SpecObject(functions, custom_types, constants, ssz_objects, dataclasses)
CONFIG_LOADER = '''
@ -104,12 +119,15 @@ from eth2spec.utils import bls
from eth2spec.utils.hash_function import hash
SSZObject = TypeVar('SSZObject', bound=View)
CONFIG_NAME = 'mainnet'
'''
PHASE1_IMPORTS = '''from eth2spec.phase0 import spec as phase0
from eth2spec.config.config_util import apply_constants_config
from typing import (
Any, Dict, Set, Sequence, NewType, Tuple, TypeVar, Callable, Optional
)
from typing import List as PyList
from dataclasses import (
dataclass,
@ -135,10 +153,14 @@ reload(phase0)
SSZVariableName = str
GeneralizedIndex = NewType('GeneralizedIndex', int)
SSZObject = TypeVar('SSZObject', bound=View)
CONFIG_NAME = 'mainnet'
'''
SUNDRY_CONSTANTS_FUNCTIONS = '''
def ceillog2(x: uint64) -> int:
return (x - 1).bit_length()
def ceillog2(x: int) -> uint64:
if x < 1:
raise ValueError(f"ceillog2 accepts only positive values, x={x}")
return uint64((x - 1).bit_length())
'''
PHASE0_SUNDRY_FUNCTIONS = '''
def get_eth1_data(block: Eth1Block) -> Eth1Data:
@ -214,19 +236,13 @@ get_attesting_indices = cache_this(
PHASE1_SUNDRY_FUNCTIONS = '''
def get_block_data_merkle_root(data: ByteList) -> Root:
# To get the Merkle root of the block data, we need the Merkle root without the length mix-in
# The below implements this in the Remerkleable framework
return Root(data.get_backing().get_left().merkle_root())
_get_start_shard = get_start_shard
get_start_shard = cache_this(
lambda state, slot: (state.validators.hash_tree_root(), slot),
_get_start_shard, lru_size=SLOTS_PER_EPOCH * 3)'''
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
def objects_to_spec(spec_object: SpecObject, imports: str, fork: str, ordered_class_objects: Dict[str, str]) -> str:
"""
Given all the objects that constitute a spec, combine them into a single pyfile.
"""
@ -246,7 +262,7 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
if k == "BLS12_381_Q":
spec_object.constants[k] += " # noqa: E501"
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, spec_object.constants[x]), spec_object.constants))
ssz_objects_instantiation_spec = '\n\n'.join(spec_object.ssz_objects.values())
ordered_class_objects_spec = '\n\n'.join(ordered_class_objects.values())
spec = (
imports
+ '\n\n' + f"fork = \'{fork}\'\n"
@ -254,7 +270,7 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str:
+ '\n' + SUNDRY_CONSTANTS_FUNCTIONS
+ '\n\n' + constants_spec
+ '\n\n' + CONFIG_LOADER
+ '\n\n' + ssz_objects_instantiation_spec
+ '\n\n' + ordered_class_objects_spec
+ '\n\n' + functions_spec
+ '\n' + PHASE0_SUNDRY_FUNCTIONS
)
@ -280,11 +296,12 @@ ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature',
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'ByteList', 'ByteVector'
'bytes', 'byte', 'ByteList', 'ByteVector',
'Dict', 'dict', 'field',
]
def dependency_order_ssz_objects(objects: Dict[str, str], custom_types: Dict[str, str]) -> None:
def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[str, str]) -> None:
"""
Determines which SSZ Object is dependent on which other and orders them appropriately
"""
@ -321,13 +338,14 @@ def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject:
"""
Takes in two spec variants (as tuples of their objects) and combines them using the appropriate combiner function.
"""
functions0, custom_types0, constants0, ssz_objects0 = spec0
functions1, custom_types1, constants1, ssz_objects1 = spec1
functions0, custom_types0, constants0, ssz_objects0, dataclasses0 = spec0
functions1, custom_types1, constants1, ssz_objects1, dataclasses1 = spec1
functions = combine_functions(functions0, functions1)
custom_types = combine_constants(custom_types0, custom_types1)
constants = combine_constants(constants0, constants1)
ssz_objects = combine_ssz_objects(ssz_objects0, ssz_objects1, custom_types)
return SpecObject(functions, custom_types, constants, ssz_objects)
dataclasses = combine_functions(dataclasses0, dataclasses1)
return SpecObject(functions, custom_types, constants, ssz_objects, dataclasses)
fork_imports = {
@ -343,9 +361,10 @@ def build_spec(fork: str, source_files: List[str]) -> str:
for value in all_specs[1:]:
spec_object = combine_spec_objects(spec_object, value)
dependency_order_ssz_objects(spec_object.ssz_objects, spec_object.custom_types)
class_objects = {**spec_object.ssz_objects, **spec_object.dataclasses}
dependency_order_class_objects(class_objects, spec_object.custom_types)
return objects_to_spec(spec_object, fork_imports[fork], fork)
return objects_to_spec(spec_object, fork_imports[fork], fork, class_objects)
class PySpecCommand(Command):
@ -382,12 +401,14 @@ class PySpecCommand(Command):
specs/phase0/beacon-chain.md
specs/phase0/fork-choice.md
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
"""
elif self.spec_fork == "phase1":
self.md_doc_paths = """
specs/phase0/beacon-chain.md
specs/phase0/fork-choice.md
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
specs/phase1/custody-game.md
specs/phase1/beacon-chain.md
specs/phase1/shard-transition.md

View File

@ -0,0 +1,28 @@
The contract logic here is based on the deposit contract written in Vyper.
The original repository (https://github.com/ethereum/deposit_contract) had the following authors:
@NIC619
@carver
@hwwhww
@davesque
@ralexstokes
@ChihChengLiang
@nisdas
@djrtwo
@JustinDrake
@vbuterin
@njgheorghita
@CarlBeek
In eth2.0-specs repository (https://github.com/ethereum/eth2.0-specs), its current location, it had the following additional authors:
@daejunpark
And this repository has the following authors:
@axic
@MrChico
@chriseth
(All of the above are GitHub usernames in order of they appeared first in the commit history.)

View File

@ -0,0 +1,43 @@
# Deposit Contract
## History
This is a rewrite of the [Vyper Eth 2.0 deposit contract](https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/deposit_contract/contracts/validator_registration.vy) to Solidity.
The original motivation was to run the SMTChecker and the new Yul IR generator option (`--ir`) in the compiler.
As of June 2020, version `r1` of the Solidity deposit contract has been verified and is considered for adoption.
See this [blog post](https://blog.ethereum.org/2020/06/23/eth2-quick-update-no-12/) for more information.
In August 2020, version `r2` was released with metadata modifications and relicensed to CC0-1.0. Afterward, this contract has been ported back to from [`axic/eth2-deposit-contract`](https://github.com/axic/eth2-deposit-contract) to this repository and replaced the Vyper deposit contract.
## Compiling solidity deposit contract
In the `eth2.0-specs` directory run:
```sh
make compile_deposit_contract
```
The following parameters were used to generate the bytecode for the `DepositContract` available in this repository:
* Contract Name: `DepositContract`
* Compiler Version: Solidity `v0.6.11+commit.5ef660b1`
* Optimization Enabled: `Yes` with `5000000` runs
* Metadata Options: `--metadata-literal` (to verify metadata hash)
```sh
solc --optimize --optimize-runs 5000000 --metadata-literal --bin deposit_contract.sol
```
## Running web3 tests
1. In the `eth2.0-specs` directory run `make install_deposit_contract_web3_tester` to install the tools needed (make sure to have Python 3.7 and pip installed).
2. In the `eth2.0-specs` directory run `make test_deposit_contract_web3_tests` to execute the tests.
## Running randomized `dapp` tests:
Install the latest version of `dapp` by following the instructions at [dapp.tools](https://dapp.tools/). Then in the `eth2.0-specs` directory run:
```sh
make test_deposit_contract
```

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,178 @@
//
//
//
//
//
//
//
//
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.6.11;
// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
/// @notice A processed deposit event.
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
/// @notice Submit a Phase 0 DepositData object.
/// @param pubkey A BLS12-381 public key.
/// @param withdrawal_credentials Commitment to a public key for withdrawals.
/// @param signature A BLS12-381 signature.
/// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
/// Used as a protection against malformed input.
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;
/// @notice Query the current deposit root hash.
/// @return The deposit root hash.
function get_deposit_root() external view returns (bytes32);
/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
}
// Based on official specification in https://eips.ethereum.org/EIPS/eip-165
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceId` and
/// `interfaceId` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}
// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity.
// It tries to stay as close as possible to the original source code.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
contract DepositContract is IDepositContract, ERC165 {
uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
// NOTE: this also ensures `deposit_count` will fit into 64-bits
uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1;
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch;
uint256 deposit_count;
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes;
constructor() public {
// Compute hashes in empty sparse Merkle tree
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)
zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));
}
function get_deposit_root() override external view returns (bytes32) {
bytes32 node;
uint size = deposit_count;
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
if ((size & 1) == 1)
node = sha256(abi.encodePacked(branch[height], node));
else
node = sha256(abi.encodePacked(node, zero_hashes[height]));
size /= 2;
}
return sha256(abi.encodePacked(
node,
to_little_endian_64(uint64(deposit_count)),
bytes24(0)
));
}
function get_deposit_count() override external view returns (bytes memory) {
return to_little_endian_64(uint64(deposit_count));
}
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) override external payable {
// Extended ABI length checks since dynamic types are used.
require(pubkey.length == 48, "DepositContract: invalid pubkey length");
require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");
require(signature.length == 96, "DepositContract: invalid signature length");
// Check deposit amount
require(msg.value >= 1 ether, "DepositContract: deposit value too low");
require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");
uint deposit_amount = msg.value / 1 gwei;
require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");
// Emit `DepositEvent` log
bytes memory amount = to_little_endian_64(uint64(deposit_amount));
emit DepositEvent(
pubkey,
withdrawal_credentials,
amount,
signature,
to_little_endian_64(uint64(deposit_count))
);
// Compute deposit data root (`DepositData` hash tree root)
bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0)));
bytes32 signature_root = sha256(abi.encodePacked(
sha256(abi.encodePacked(signature[:64])),
sha256(abi.encodePacked(signature[64:], bytes32(0)))
));
bytes32 node = sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, bytes24(0), signature_root))
));
// Verify computed and expected deposit data roots match
require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root");
// Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)
require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full");
// Add deposit data root to Merkle tree (update a single `branch` node)
deposit_count += 1;
uint size = deposit_count;
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
if ((size & 1) == 1) {
branch[height] = node;
return;
}
node = sha256(abi.encodePacked(branch[height], node));
size /= 2;
}
// As the loop should always end prematurely with the `return` statement,
// this code should be unreachable. We assert `false` just to be safe.
assert(false);
}
function supportsInterface(bytes4 interfaceId) override external pure returns (bool) {
return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId;
}
function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
ret = new bytes(8);
bytes8 bytesValue = bytes8(value);
// Byteswapping during copying to bytes.
ret[0] = bytesValue[7];
ret[1] = bytesValue[6];
ret[2] = bytesValue[5];
ret[3] = bytesValue[4];
ret[4] = bytesValue[3];
ret[5] = bytesValue[2];
ret[6] = bytesValue[1];
ret[7] = bytesValue[0];
}
}

@ -0,0 +1 @@
Subproject commit eb7148d43c1ca6f9890361e2e2378364af2430ba

View File

@ -0,0 +1,18 @@
let dapptools = builtins.fetchGit {
url = "https://github.com/dapphub/dapptools.git";
rev = "11dcefe1f03b0acafe76b4d7d54821ef6bd63131";
};
nixpkgs = builtins.fetchGit {
url = "https://github.com/nixos/nixpkgs";
ref = "release-19.03";
rev = "f1707d8875276cfa110139435a7e8998b4c2a4fd";
};
pkgs-for-dapp = import nixpkgs {
overlays = [
(import (dapptools + /overlay.nix))
];
};
in
pkgs-for-dapp.mkShell {
buildInputs = [ pkgs-for-dapp.dapp pkgs-for-dapp.solc pkgs-for-dapp.hevm ];
}

View File

@ -0,0 +1,142 @@
pragma solidity ^0.6.2;
import "../lib/ds-test/src/test.sol";
import "./vyper_setup.sol";
import "../deposit_contract.sol";
contract DepositContractTest is DSTest {
DepositContract depositContract_sol;
DepositContract depositContract_vyp;
uint64 constant GWEI = 1000000000;
function setUp() public {
VyperSetup vyperSetup = new VyperSetup();
depositContract_vyp = DepositContract(vyperSetup.deployDeposit());
depositContract_sol = new DepositContract();
}
// --- SUCCESS TESTS ---
// Tests initialized storage values, comparing vyper and solidity
function test_empty_root() public {
bytes32 zHash = 0x0000000000000000000000000000000000000000000000000000000000000000;
bytes32 zHashN = zHash;
for (uint i = 0; i <= 31; i++) {
zHashN = sha256(abi.encodePacked(zHashN, zHashN));
}
assertEq(sha256(abi.encodePacked(zHashN, zHash)), depositContract_vyp.get_deposit_root());
assertEq(depositContract_sol.get_deposit_root(), depositContract_vyp.get_deposit_root());
}
// Generates 16 random deposits, insert them in both vyper and solidity version and compare get_deposit_root after each insertion
function test_16_deposits(bytes32[16] memory pubkey_one, bytes16[16] memory pubkey_two, bytes32[16] memory _withdrawal_credentials,
bytes32[16] memory sig_one, bytes32[16] memory sig_two, bytes32[16] memory sig_three, uint32[16] memory amount) public {
uint j;
for (uint i = 0; i < 16; i++) {
// as of dcaa774, the solidity version is more restrictive than vyper and requires deposits to be divisible by GWEI
uint64 gweiamount = amount[i] * GWEI;
if (1 ether <= gweiamount) {
j++;
deposit_in(depositContract_sol, pubkey_one[i], pubkey_two[i], _withdrawal_credentials[i], sig_one[i], sig_two[i], sig_three[i], gweiamount);
deposit_in(depositContract_vyp, pubkey_one[i], pubkey_two[i], _withdrawal_credentials[i], sig_one[i], sig_two[i], sig_three[i], gweiamount);
assertEq(depositContract_sol.get_deposit_root(), depositContract_vyp.get_deposit_root());
assertEq(keccak256(abi.encodePacked(depositContract_sol.get_deposit_count())), keccak256(abi.encodePacked(depositContract_vyp.get_deposit_count())));
assertEq(keccak256(abi.encodePacked(depositContract_sol.get_deposit_count())), keccak256(to_little_endian_64(uint64(j))));
}
}
}
// The solidity contract fails when given a deposit which is not divisible by GWEI
function testFail_deposit_not_divisible_by_gwei(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials,
bytes32 sig_one, bytes32 sig_two, bytes32 sig_three) public {
deposit_in(depositContract_sol, pubkey_one, pubkey_two, _withdrawal_credentials, sig_one, sig_two, sig_three, 1 ether + 1);
}
// --- FAILURE TESTS ---
// if the node is given randomly instead of as the ssz root, the chances of success are so unlikely that we can assert it to be false
function testFail_malformed_node_vyp(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one,
bytes32 sig_two, bytes32 sig_three, uint64 amount, bytes32 node) public {
bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two);
bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials); //I wish just recasting to `bytes` would work..
bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three);
depositContract_vyp.deposit{value: amount}(pubkey, withdrawal_credentials, signature, node);
}
// If the node is taken randomly instead of as the ssz root, the chances of success are so unlikely that we can assert it to be false
function testFail_malformed_node_sol(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one,
bytes32 sig_two, bytes32 sig_three, uint64 amount, bytes32 node) public {
bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two);
bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials);
bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three);
depositContract_sol.deposit{value: amount}(pubkey, withdrawal_credentials, signature, node);
}
// if bytes lengths are wrong, the call will fail
function testFail_malformed_calldata_vyp(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, uint64 amount) public {
if (amount >= 1000000000000000000) {
if (!(pubkey.length == 48 && withdrawal_credentials.length == 32 && signature.length == 96)) {
depositContract_vyp.deposit{value: amount}(pubkey, withdrawal_credentials, signature,
encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI))
);
} else { revert(); }
} else { revert(); }
}
function testFail_malformed_calldata_sol(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, uint64 amount) public {
if (amount >= 1000000000000000000) {
if (!(pubkey.length == 48 && withdrawal_credentials.length == 32 && signature.length == 96)) {
depositContract_sol.deposit{value: amount}(pubkey, withdrawal_credentials, signature,
encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI))
);
} else { revert(); }
} else { revert(); }
}
// --- HELPER FUNCTIONS ---
function deposit_in(DepositContract depositContract, bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one, bytes32 sig_two, bytes32 sig_three, uint64 amount) public {
bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two);
bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials);
bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three);
bytes32 node = encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI));
depositContract.deposit{value: amount}(pubkey, withdrawal_credentials, signature, node);
}
function slice(bytes memory a, uint32 offset, uint32 size) pure internal returns (bytes memory result) {
result = new bytes(size);
for (uint i = 0; i < size; i++) {
result[i] = a[offset + i];
}
}
function encode_node(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, bytes memory amount) public pure returns (bytes32) {
bytes16 zero_bytes16;
bytes24 zero_bytes24;
bytes32 zero_bytes32;
bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, zero_bytes16));
bytes32 signature_root = sha256(abi.encodePacked(
sha256(abi.encodePacked(slice(signature, 0, 64))),
sha256(abi.encodePacked(slice(signature, 64, 32), zero_bytes32))
));
return sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, zero_bytes24, signature_root))
));
}
function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
ret = new bytes(8);
ret[0] = bytes1(uint8(value & 0xff));
ret[1] = bytes1(uint8((value >> 8) & 0xff));
ret[2] = bytes1(uint8((value >> 16) & 0xff));
ret[3] = bytes1(uint8((value >> 24) & 0xff));
ret[4] = bytes1(uint8((value >> 32) & 0xff));
ret[5] = bytes1(uint8((value >> 40) & 0xff));
ret[6] = bytes1(uint8((value >> 48) & 0xff));
ret[7] = bytes1(uint8((value >> 56) & 0xff));
}
}

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ DIR = os.path.dirname(__file__)
def get_deposit_contract_json():
file_path = os.path.join(DIR, '../../contracts/validator_registration.json')
file_path = os.path.join(DIR, '../../deposit_contract.json')
deposit_contract_json = open(file_path).read()
return json.loads(deposit_contract_json)

View File

@ -6,7 +6,7 @@ from eth2spec.phase0.spec import DepositData
from eth2spec.utils.ssz.ssz_typing import List
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from deposit_contract.conftest import (
from tests.conftest import (
FULL_DEPOSIT_AMOUNT,
MIN_DEPOSIT_AMOUNT,
)

View File

@ -195,6 +195,7 @@ The following values are (non-configurable) constants used throughout the specif
| `HYSTERESIS_QUOTIENT` | `uint64(4)` |
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` |
| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` |
| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(3)` |
- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
@ -602,7 +603,7 @@ def bytes_to_uint64(data: bytes) -> uint64:
#### BLS Signatures
Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-02](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02) but uses [Hashing to Elliptic Curves - draft-irtf-cfrg-hash-to-curve-07](https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-07) instead of draft-irtf-cfrg-hash-to-curve-06. Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces:
Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-03](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03). Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces:
- `def Sign(SK: int, message: Bytes) -> BLSSignature`
- `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
@ -612,8 +613,6 @@ Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specificati
Within these specifications, BLS signatures are treated as a module for notational clarity, thus to verify a signature `bls.Verify(...)` is used.
*Note*: The non-standard configuration of the BLS and hash to curve specs is temporary and will be resolved once IETF releases BLS spec draft 3.
### Predicates
#### `is_active_validator`
@ -773,7 +772,7 @@ def compute_committee(indices: Sequence[ValidatorIndex],
Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``.
"""
start = (len(indices) * index) // count
end = (len(indices) * (index + 1)) // count
end = (len(indices) * uint64(index + 1)) // count
return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)]
```
@ -1305,6 +1304,8 @@ def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAtte
```python
def process_justification_and_finalization(state: BeaconState) -> None:
# Initial FFG checkpoint values have a `0x00` stub for `root`.
# Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub.
if get_current_epoch(state) <= GENESIS_EPOCH + 1:
return
@ -1452,7 +1453,7 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ
if index in get_attesting_indices(state, a.data, a.aggregation_bits)
], key=lambda a: a.inclusion_delay)
rewards[attestation.proposer_index] += get_proposer_reward(state, index)
max_attester_reward = get_base_reward(state, index) - get_proposer_reward(state, index)
max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index))
rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay)
# No penalties associated with inclusion delay
@ -1512,6 +1513,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
```python
def process_rewards_and_penalties(state: BeaconState) -> None:
# No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch
if get_current_epoch(state) == GENESIS_EPOCH:
return
@ -1551,10 +1553,11 @@ def process_registry_updates(state: BeaconState) -> None:
def process_slashings(state: BeaconState) -> None:
epoch = get_current_epoch(state)
total_balance = get_total_active_balance(state)
adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance)
for index, validator in enumerate(state.validators):
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
penalty_numerator = validator.effective_balance // increment * min(sum(state.slashings) * 3, total_balance)
penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
penalty = penalty_numerator // total_balance * increment
decrease_balance(state, ValidatorIndex(index), penalty)
```
@ -1571,7 +1574,7 @@ def process_final_updates(state: BeaconState) -> None:
# Update effective balances with hysteresis
for index, validator in enumerate(state.validators):
balance = state.balances[index]
HYSTERESIS_INCREMENT = EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT
HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT)
DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
if (

View File

@ -16,7 +16,7 @@
- [Deposit amount](#deposit-amount)
- [Withdrawal credentials](#withdrawal-credentials)
- [`DepositEvent` log](#depositevent-log)
- [Vyper code](#vyper-code)
- [Solidity code](#solidity-code)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -53,7 +53,7 @@ _Note_: See [here](https://chainid.network/) for a comprehensive list of public
### `deposit` function
The deposit contract has a public `deposit` function to make deposits. It takes as arguments `pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32`. The first three arguments populate a [`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata.
The deposit contract has a public `deposit` function to make deposits. It takes as arguments `bytes calldata pubkey, bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root`. The first three arguments populate a [`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata.
#### Deposit amount
@ -72,8 +72,8 @@ The private key corresponding to `withdrawal_pubkey` will be required to initiat
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
## Solidity code
The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.vy).
The deposit contract source code, written in Solidity, is available [here](../../solidity_deposit_contract/deposit_contract.sol).
*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

@ -98,14 +98,10 @@ This should be the genesis state for a full client.
*Note* With regards to fork choice, block headers are interchangeable with blocks. The spec is likely to move to headers for reduced overhead in test vectors and better encapsulation. Full implementations store blocks as part of their database and will often use full blocks when dealing with production fork choice.
_The block for `anchor_root` is incorrectly initialized to the block header, rather than the full block. This does not affect functionality but will be cleaned up in subsequent releases._
```python
def get_forkchoice_store(anchor_state: BeaconState) -> Store:
anchor_block_header = copy(anchor_state.latest_block_header)
if anchor_block_header.state_root == Bytes32():
anchor_block_header.state_root = hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block_header)
def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store:
assert anchor_block.state_root == hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block)
anchor_epoch = get_current_epoch(anchor_state)
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
@ -115,7 +111,7 @@ def get_forkchoice_store(anchor_state: BeaconState) -> Store:
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
best_justified_checkpoint=justified_checkpoint,
blocks={anchor_root: anchor_block_header},
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: copy(anchor_state)},
checkpoint_states={justified_checkpoint: copy(anchor_state)},
)

View File

@ -243,12 +243,11 @@ Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/
Clients MUST reject (fail validation) messages that are over this size limit.
Likewise, clients MUST NOT emit or propagate messages larger than this limit.
The `message-id` of a gossipsub message MUST be:
The `message-id` of a gossipsub message MUST be the first 8 bytes of the SHA-256 hash of the message data, i.e.:
```python
message-id: base64(SHA256(message.data))
message-id: SHA256(message.data)[0:8]
```
where `base64` is the [URL-safe base64 alphabet](https://tools.ietf.org/html/rfc4648#section-3.2) with padding characters omitted.
The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic:
@ -383,6 +382,7 @@ The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated a
to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`.
The following validations MUST pass before forwarding the `attestation` on the subnet.
- _[REJECT]_ The committee index is within the expected range -- i.e. `data.index < get_committee_count_per_slot(state, data.target.epoch)`.
- _[REJECT]_ The attestation is for the correct subnet --
i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`,
where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`,
@ -391,8 +391,12 @@ The following validations MUST pass before forwarding the `attestation` on the s
(within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`
(a client MAY queue future attestations for processing at the appropriate slot).
- _[REJECT]_ The attestation's epoch matches its target -- i.e. `attestation.data.target.epoch ==
compute_epoch_at_slot(attestation.data.slot)`
- _[REJECT]_ The attestation is unaggregated --
that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set).
that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set).
- _[REJECT]_ The number of aggregation bits matches the committee size -- i.e.
`len(attestation.aggregation_bits) == len(get_beacon_committee(state, data.slot, data.index))`.
- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet
that has an identical `attestation.data.target.epoch` and participating validator index.
- _[REJECT]_ The signature of `attestation` is valid.
@ -400,6 +404,8 @@ The following validations MUST pass before forwarding the `attestation` on the s
(via both gossip and non-gossip sources)
(a client MAY queue aggregates for processing once block is retrieved).
- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation.
- _[REJECT]_ The attestation's target block is an ancestor of the block named in the LMD vote -- i.e.
`get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(attestation.data.target.epoch)) == attestation.data.target.root`
- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `attestation.data.beacon_block_root` -- i.e.
`get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch))
== store.finalized_checkpoint.root`
@ -723,7 +729,7 @@ The request MUST be encoded as an SSZ-container.
The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period
Clients MUST keep a record of signed blocks seen since the start of the weak subjectivity period
and MUST support serving requests of blocks up to their own `head_block_root`.
Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks.

View File

@ -168,7 +168,7 @@ def get_committee_assignment(state: BeaconState,
* ``assignment[2]`` is the slot at which the committee is assigned
Return None if no assignment.
"""
next_epoch = get_current_epoch(state) + 1
next_epoch = Epoch(get_current_epoch(state) + 1)
assert epoch <= next_epoch
start_slot = compute_start_slot_at_epoch(epoch)
@ -194,9 +194,14 @@ def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool:
### Lookahead
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing, which must be checked during the epoch in question.
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead
on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot.
Note that this lookahead does not apply to proposing, which must be checked during the epoch in question.
`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments by noting at which future slot they will have to attest and joining the committee index attestation subnet related to their committee assignment.
`get_committee_assignment` should be called at the start of each epoch
to get the assignment for the next epoch (`current_epoch + 1`).
A validator should plan for future assignments by noting their assigned attestation
slot and joining the committee index attestation subnet related to their committee assignment.
Specifically a validator should:
* Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
@ -214,9 +219,18 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo
### Block proposal
A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) 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](./beacon-chain.md#beacon-chain-state-transition-function).
A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) 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](./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 312,500 validators = 10 million ETH, that's once per ~6 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 ~6 weeks).
*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied.
That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`.
#### Preparing for a `BeaconBlock`
@ -251,7 +265,13 @@ def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) ->
##### Eth1 Data
The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data. This recent data contains an Eth1 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth1 block. If over half of the block proposers in the current Eth1 voting period vote for the same `eth1_data` then `state.eth1_data` updates immediately allowing new deposits to be processed. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data.
This recent data contains an Eth1 block hash as well as the associated deposit root
(as calculated by the `get_deposit_root()` method of the deposit contract) and
deposit count after execution of the corresponding Eth1 block.
If over half of the block proposers in the current Eth1 voting period vote for the same
`eth1_data` then `state.eth1_data` updates immediately allowing new deposits to be processed.
Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
###### `Eth1Block`
@ -440,7 +460,7 @@ def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, comm
Compute the correct subnet for an attestation for Phase 0.
Note, this mimics expected Phase 1 behavior where attestations will be mapped to their shard subnet.
"""
slots_since_epoch_start = slot % SLOTS_PER_EPOCH
slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH)
committees_since_epoch_start = committees_per_slot * slots_since_epoch_start
return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT)

View File

@ -0,0 +1,136 @@
# Ethereum 2.0 Phase 0 -- Weak Subjectivity Guide
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Constants](#constants)
- [Weak Subjectivity Checkpoint](#weak-subjectivity-checkpoint)
- [Weak Subjectivity Period](#weak-subjectivity-period)
- [Calculating the Weak Subjectivity Period](#calculating-the-weak-subjectivity-period)
- [Weak Subjectivity Sync](#weak-subjectivity-sync)
- [Weak Subjectivity Sync Procedure](#weak-subjectivity-sync-procedure)
- [Checking for Stale Weak Subjectivity Checkpoint](#checking-for-stale-weak-subjectivity-checkpoint)
- [Distributing Weak Subjectivity Checkpoints](#distributing-weak-subjectivity-checkpoints)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
This document is a guide for implementing the Weak Subjectivity protections in Phase 0 of Ethereum 2.0.
This document is still a work-in-progress, and is subject to large changes.
For more information about weak subjectivity and why it is required, please refer to:
- [Weak Subjectivity in Eth2.0](https://notes.ethereum.org/@adiasg/weak-subjectvity-eth2)
- [Proof of Stake: How I Learned to Love Weak Subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/)
## Prerequisites
This document uses data structures, constants, functions, and terminology from
[Phase 0 -- The Beacon Chain](./beacon-chain.md) and [Phase 0 -- Beacon Chain Fork Choice](./fork-choice.md).
## Constants
| Name | Value |
|----------------|--------------|
| `SAFETY_DECAY` | `uint64(10)` |
## Weak Subjectivity Checkpoint
Any `Checkpoint` can used be a Weak Subjectivity Checkpoint.
These Weak Subjectivity Checkpoints are distributed by providers,
downloaded by users and/or distributed as a part of clients, and used as input while syncing a client.
## Weak Subjectivity Period
The Weak Subjectivity Period is the number of recent epochs within which there
must be a Weak Subjectivity Checkpoint to ensure that an attacker who takes control
of the validator set at the beginning of the period is slashed at least a minimum threshold
in the event that a conflicting `Checkpoint` is finalized.
`SAFETY_DECAY` is defined as the maximum percentage tolerable loss in the one-third
safety margin of FFG finality. Thus, any attack exploiting the Weak Subjectivity Period has
a safety margin of at least `1/3 - SAFETY_DECAY/100`.
### Calculating the Weak Subjectivity Period
*Note*: `compute_weak_subjectivity_period()` is planned to be updated when a more accurate calculation is made.
```python
def compute_weak_subjectivity_period(state: BeaconState) -> uint64:
weak_subjectivity_period = MIN_VALIDATOR_WITHDRAWABILITY_DELAY
validator_count = len(get_active_validator_indices(state, get_current_epoch(state)))
if validator_count >= MIN_PER_EPOCH_CHURN_LIMIT * CHURN_LIMIT_QUOTIENT:
weak_subjectivity_period += SAFETY_DECAY * CHURN_LIMIT_QUOTIENT / (2 * 100)
else:
weak_subjectivity_period += SAFETY_DECAY * validator_count / (2 * 100 * MIN_PER_EPOCH_CHURN_LIMIT)
return weak_subjectivity_period
```
*Details about the calculation*:
- `100` appears in the denominator to get the actual percentage ratio from `SAFETY_DECAY`
- For more information about other terms in this equation, refer to
[Weak Subjectivity in Eth2.0](https://notes.ethereum.org/@adiasg/weak-subjectvity-eth2)
A brief reference for what these values look like in practice:
| `validator_count` | `weak_subjectivity_period` |
| ---- | ---- |
| 1024 | 268 |
| 2048 | 281 |
| 4096 | 307 |
| 8192 | 358 |
| 16384 | 460 |
| 32768 | 665 |
| 65536 | 1075 |
| 131072 | 1894 |
| 262144 | 3532 |
| 524288 | 3532 |
## Weak Subjectivity Sync
Clients should allow users to input a Weak Subjectivity Checkpoint at startup, and guarantee that any successful sync leads to the given Weak Subjectivity Checkpoint along the canonical chain. If such a sync is not possible, the client should treat this as a critical and irrecoverable failure.
### Weak Subjectivity Sync Procedure
1. Input a Weak Subjectivity Checkpoint as a CLI parameter in `block_root:epoch_number` format,
where `block_root` (an "0x" prefixed 32-byte hex string) and `epoch_number` (an integer) represent a valid `Checkpoint`.
Example of the format:
```
0x8584188b86a9296932785cc2827b925f9deebacce6d72ad8d53171fa046b43d9:9544
```
2. - *IF* `epoch_number > store.finalized_checkpoint.epoch`,
then *ASSERT* during block sync that block with root `block_root` is in the sync path at epoch `epoch_number`.
Emit descriptive critical error if this assert fails, then exit client process.
- *IF* `epoch_number <= store.finalized_checkpoint.epoch`,
then *ASSERT* that the block in the canonical chain at epoch `epoch_number` has root `block_root`.
Emit descriptive critical error if this assert fails, then exit client process.
### Checking for Stale Weak Subjectivity Checkpoint
Clients may choose to validate that the input Weak Subjectivity Checkpoint is not stale at the time of startup.
To support this mechanism, the client needs to take the state at the Weak Subjectivity Checkpoint as
a CLI parameter input (or fetch the state associated with the input Weak Subjectivity Checkpoint from some source).
The check can be implemented in the following way:
```python
def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool:
# Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint
assert ws_state.latest_block_header.state_root == ws_checkpoint.root
assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch
ws_period = compute_weak_subjectivity_period(ws_state)
ws_state_epoch = compute_epoch_at_slot(ws_state.slot)
current_epoch = compute_epoch_at_slot(get_current_slot(store))
return current_epoch <= ws_state_epoch + ws_period
```
## Distributing Weak Subjectivity Checkpoints
This section will be updated soon.

View File

@ -35,7 +35,6 @@
- [`ShardState`](#shardstate)
- [`ShardTransition`](#shardtransition)
- [`CompactCommittee`](#compactcommittee)
- [`AttestationCustodyBitWrapper`](#attestationcustodybitwrapper)
- [Helper functions](#helper-functions)
- [Misc](#misc-1)
- [`compute_previous_slot`](#compute_previous_slot)
@ -47,6 +46,7 @@
- [`compute_updated_gasprice`](#compute_updated_gasprice)
- [`compute_committee_source_epoch`](#compute_committee_source_epoch)
- [Beacon state accessors](#beacon-state-accessors)
- [Updated `get_committee_count_per_slot`](#updated-get_committee_count_per_slot)
- [`get_active_shard_count`](#get_active_shard_count)
- [`get_online_validator_indices`](#get_online_validator_indices)
- [`get_shard_committee`](#get_shard_committee)
@ -105,19 +105,20 @@ Configuration is not namespaced. Instead it is strictly an extension;
| Name | Value |
| - | - |
| `MAX_SHARDS` | `2**10` (= 1024) |
| `LIGHT_CLIENT_COMMITTEE_SIZE` | `2**7` (= 128) |
| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) |
| `MAX_SHARDS` | `uint64(2**10)` (= 1024) |
| `INITIAL_ACTIVE_SHARDS` | `uint64(2**6)` (= 64) |
| `LIGHT_CLIENT_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) |
| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `uint64(2**3)` (= 8) |
### Shard block configs
| Name | Value | Unit |
| - | - | - |
| `MAX_SHARD_BLOCK_SIZE` | `2**20` (= 1,048,576) | bytes |
| `TARGET_SHARD_BLOCK_SIZE` | `2**18` (= 262,144) | bytes |
| `SHARD_BLOCK_OFFSETS` | `[1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]` | - |
| `MAX_SHARD_BLOCK_SIZE` | `uint64(2**20)` (= 1,048,576) | bytes |
| `TARGET_SHARD_BLOCK_SIZE` | `uint64(2**18)` (= 262,144) | bytes |
| `SHARD_BLOCK_OFFSETS` | `List[uint64, 12]([1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233])` | - |
| `MAX_SHARD_BLOCKS_PER_ATTESTATION` | `len(SHARD_BLOCK_OFFSETS)` | - |
| `BYTES_PER_CUSTODY_CHUNK` | `2**12` (= 4,096) | bytes |
| `BYTES_PER_CUSTODY_CHUNK` | `uint64(2**12)` (= 4,096) | bytes |
| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - |
### Gwei values
@ -408,15 +409,6 @@ class CompactCommittee(Container):
compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
```
### `AttestationCustodyBitWrapper`
```python
class AttestationCustodyBitWrapper(Container):
attestation_data_root: Root
block_index: uint64
bit: boolean
```
## Helper functions
### Misc
@ -512,7 +504,7 @@ def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch:
"""
Return the source epoch for computing the committee.
"""
source_epoch = epoch - epoch % period
source_epoch = Epoch(epoch - epoch % period)
if source_epoch >= period:
source_epoch -= period # `period` epochs lookahead
return source_epoch
@ -520,11 +512,28 @@ def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch:
### Beacon state accessors
#### Updated `get_committee_count_per_slot`
```python
def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64:
"""
Return the number of committees in each slot for the given ``epoch``.
"""
return max(uint64(1), min(
get_active_shard_count(state),
uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
))
```
#### `get_active_shard_count`
```python
def get_active_shard_count(state: BeaconState) -> uint64:
return len(state.shard_states) # May adapt in the future, or change over time.
"""
Return the number of active shards.
Note that this puts an upper bound on the number of committees per slot.
"""
return INITIAL_ACTIVE_SHARDS
```
#### `get_online_validator_indices`
@ -545,12 +554,11 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -
source_epoch = compute_committee_source_epoch(epoch, SHARD_COMMITTEE_PERIOD)
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
active_shard_count = get_active_shard_count(beacon_state)
return compute_committee(
indices=active_validator_indices,
seed=seed,
index=shard,
count=active_shard_count,
count=get_active_shard_count(beacon_state),
)
```
@ -567,7 +575,7 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque
return compute_committee(
indices=active_validator_indices,
seed=seed,
index=0,
index=uint64(0),
count=get_active_shard_count(beacon_state),
)[:LIGHT_CLIENT_COMMITTEE_SIZE]
```
@ -593,10 +601,10 @@ def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: S
"""
Return the sum of committee counts in range ``[start_slot, stop_slot)``.
"""
return sum(
return uint64(sum(
get_committee_count_per_slot(state, compute_epoch_at_slot(Slot(slot)))
for slot in range(start_slot, stop_slot)
)
))
```
#### `get_start_shard`
@ -617,10 +625,11 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
else:
# Previous epoch
shard_delta = get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
max_committees_per_epoch = MAX_COMMITTEES_PER_SLOT * SLOTS_PER_EPOCH
max_committees_per_slot = active_shard_count
max_committees_in_span = max_committees_per_slot * (current_epoch_start_slot - slot)
return Shard(
# Ensure positive
(state.current_epoch_start_shard + max_committees_per_epoch * active_shard_count - shard_delta)
(state.current_epoch_start_shard + max_committees_in_span - shard_delta)
% active_shard_count
)
```
@ -752,7 +761,6 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.index < get_committee_count_per_slot(state, data.target.epoch)
assert data.index < get_active_shard_count(state)
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
assert data.target.epoch == compute_epoch_at_slot(data.slot)
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH
@ -760,20 +768,24 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
committee = get_beacon_committee(state, data.slot, data.index)
assert len(attestation.aggregation_bits) == len(committee)
if attestation.data.target.epoch == get_current_epoch(state):
assert attestation.data.source == state.current_justified_checkpoint
if data.target.epoch == get_current_epoch(state):
assert data.source == state.current_justified_checkpoint
else:
assert attestation.data.source == state.previous_justified_checkpoint
assert data.source == state.previous_justified_checkpoint
# Type 1: on-time attestations
if is_on_time_attestation(state, attestation.data):
if is_on_time_attestation(state, data):
# Correct parent block root
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
# Correct shard number
shard = compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
assert attestation.data.shard == shard
# On-time attestations should have a non-empty shard transition root
assert attestation.data.shard_transition_root != hash_tree_root(ShardTransition())
shard = compute_shard_from_committee_index(state, data.index, data.slot)
assert data.shard == shard
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if data.slot > GENESIS_SLOT:
# On-time attestations should have a non-empty shard transition root
assert data.shard_transition_root != hash_tree_root(ShardTransition())
else:
assert data.shard_transition_root == hash_tree_root(ShardTransition())
# Type 2: no shard transition
else:
# Ensure delayed attestation
@ -811,7 +823,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
```python
def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None:
# TODO: only need to check it once when phase 1 starts
assert state.slot > PHASE_1_GENESIS_SLOT
assert state.slot > PHASE_1_FORK_SLOT
# Correct data root count
offset_slots = get_offset_slots(state, shard)
@ -863,9 +875,10 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr
# Verify combined proposer signature
assert optional_aggregate_verify(pubkeys, signing_roots, transition.proposer_signature_aggregate)
# Save updated state
state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1]
state.shard_states[shard].slot = compute_previous_slot(state.slot)
# Copy and save updated shard state
shard_state = copy(transition.shard_states[len(transition.shard_states) - 1])
shard_state.slot = compute_previous_slot(state.slot)
state.shard_states[shard] = shard_state
```
###### `process_crosslink_for_shard`
@ -976,8 +989,11 @@ def verify_empty_shard_transition(state: BeaconState, shard_transitions: Sequenc
def process_shard_transitions(state: BeaconState,
shard_transitions: Sequence[ShardTransition],
attestations: Sequence[Attestation]) -> None:
# Process crosslinks
process_crosslinks(state, shard_transitions, attestations)
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if compute_previous_slot(state.slot) > GENESIS_SLOT:
# Process crosslinks
process_crosslinks(state, shard_transitions, attestations)
# Verify the empty proposal shard states
assert verify_empty_shard_transition(state, shard_transitions)
```

View File

@ -25,7 +25,6 @@
- [`CustodyKeyReveal`](#custodykeyreveal)
- [`EarlyDerivedSecretReveal`](#earlyderivedsecretreveal)
- [Helpers](#helpers)
- [`get_block_data_merkle_root`](#get_block_data_merkle_root)
- [`replace_empty_or_append`](#replace_empty_or_append)
- [`legendre_bit`](#legendre_bit)
- [`get_custody_atoms`](#get_custody_atoms)
@ -57,10 +56,10 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| Name | Value | Unit |
| - | - | - |
| `CUSTODY_PRIME` | `2 ** 256 - 189` | - |
| `CUSTODY_SECRETS` | `3` | - |
| `BYTES_PER_CUSTODY_ATOM` | `32` | bytes |
| `CUSTODY_PROBABILITY_EXPONENT` | `10` | - |
| `CUSTODY_PRIME` | `int(2 ** 256 - 189)` | - |
| `CUSTODY_SECRETS` | `uint64(3)` | - |
| `BYTES_PER_CUSTODY_ATOM` | `uint64(32)` | bytes |
| `CUSTODY_PROBABILITY_EXPONENT` | `uint64(10)` | - |
## Configuration
@ -68,30 +67,29 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
| Name | Value | Unit | Duration |
| - | - | :-: | :-: |
| `RANDAO_PENALTY_EPOCHS` | `2**1` (= 2) | epochs | 12.8 minutes |
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `2**15` (= 32,768) | epochs | ~146 days |
| `EPOCHS_PER_CUSTODY_PERIOD` | `2**14` (= 16,384) | epochs | ~73 days |
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `2**11` (= 2,048) | epochs | ~9 days |
| `MAX_CHUNK_CHALLENGE_DELAY` | `2**15` (= 32,768) | epochs | ~146 days |
| `CHUNK_RESPONSE_DEADLINE` | `2**14` (= 16,384) | epochs | ~73 days |
| `RANDAO_PENALTY_EPOCHS` | `uint64(2**1)` (= 2) | epochs | 12.8 minutes |
| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `uint64(2**15)` (= 32,768) | epochs | ~146 days |
| `EPOCHS_PER_CUSTODY_PERIOD` | `uint64(2**14)` (= 16,384) | epochs | ~73 days |
| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `uint64(2**11)` (= 2,048) | epochs | ~9 days |
| `MAX_CHUNK_CHALLENGE_DELAY` | `uint64(2**15)` (= 32,768) | epochs | ~146 days |
### Max operations per block
| Name | Value |
| - | - |
| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `2**20` (= 1,048,576) |
| `MAX_CUSTODY_KEY_REVEALS` | `2**8` (= 256) |
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `2**0` (= 1) |
| `MAX_CUSTODY_CHUNK_CHALLENGES` | `2**2` (= 4) |
| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `2**4` (= 16) |
| `MAX_CUSTODY_SLASHINGS` | `2**0` (= 1) |
| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `uint64(2**20)` (= 1,048,576) |
| `MAX_CUSTODY_KEY_REVEALS` | `uint64(2**8)` (= 256) |
| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `uint64(2**0)` (= 1) |
| `MAX_CUSTODY_CHUNK_CHALLENGES` | `uint64(2**2)` (= 4) |
| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `uint64(2**4)` (= 16) |
| `MAX_CUSTODY_SLASHINGS` | `uint64(2**0)` (= 1) |
### Reward and penalty quotients
| Name | Value |
| - | - |
| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `2**1` (= 2) |
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `uint64(2**1)` (= 2) |
| `MINOR_REWARD_QUOTIENT` | `uint64(2**8)` (= 256) |
## Data structures
@ -127,7 +125,7 @@ class CustodyChunkResponse(Container):
challenge_index: uint64
chunk_index: uint64
chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK]
branch: Vector[Root, CUSTODY_RESPONSE_DEPTH]
branch: Vector[Root, CUSTODY_RESPONSE_DEPTH + 1]
```
#### `CustodySlashing`
@ -180,13 +178,8 @@ class EarlyDerivedSecretReveal(Container):
mask: Bytes32
```
## Helpers
### `get_block_data_merkle_root`
`get_block_data_merkle_root(data: ByteList) -> Root` is the function that returns the Merkle root of the block data without the length mix-in.
### `replace_empty_or_append`
```python
@ -272,12 +265,12 @@ def universal_hash_function(data_chunks: Sequence[bytes], secrets: Sequence[int]
### `compute_custody_bit`
```python
def compute_custody_bit(key: BLSSignature, data: ByteList[MAX_SHARD_BLOCK_SIZE]) -> bit:
def compute_custody_bit(key: BLSSignature, data: ByteList) -> bit:
custody_atoms = get_custody_atoms(data)
secrets = get_custody_secrets(key)
uhf = universal_hash_function(custody_atoms, secrets)
legendre_bits = [legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)]
return all(legendre_bits)
return bit(all(legendre_bits))
```
### `get_randao_epoch_for_custody_period`
@ -323,7 +316,7 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge
# Verify the attestation
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation))
# Verify it is not too late to challenge the attestation
max_attestation_challenge_epoch = challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY
max_attestation_challenge_epoch = Epoch(challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY)
assert get_current_epoch(state) <= max_attestation_challenge_epoch
# Verify it is not too late to challenge the responder
responder = state.validators[challenge.responder_index]
@ -381,7 +374,7 @@ def process_chunk_challenge_response(state: BeaconState,
assert is_valid_merkle_branch(
leaf=hash_tree_root(response.chunk),
branch=response.branch,
depth=CUSTODY_RESPONSE_DEPTH,
depth=CUSTODY_RESPONSE_DEPTH + 1, # Add 1 for the List length mix-in
index=response.chunk_index,
root=challenge.data_root,
)
@ -445,7 +438,7 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived
Note that this function mutates ``state``.
"""
revealed_validator = state.validators[reveal.revealed_index]
derived_secret_location = reveal.epoch % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS
derived_secret_location = uint64(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
@ -522,12 +515,8 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed
shard_transition = custody_slashing.shard_transition
assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root
# Verify that the provided data matches the shard-transition
assert (
get_block_data_merkle_root(custody_slashing.data)
== shard_transition.shard_data_roots[custody_slashing.data_index]
)
assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index]
assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index]
# Verify existence and participation of claimed malefactor
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
assert custody_slashing.malefactor_index in attesters

View File

@ -9,8 +9,13 @@
- [Introduction](#introduction)
- [Helpers](#helpers)
- [Extended `LatestMessage`](#extended-latestmessage)
- [Updated data structures](#updated-data-structures)
- [Extended `Store`](#extended-store)
- [New data structures](#new-data-structures)
- [`ShardLatestMessage`](#shardlatestmessage)
- [`ShardStore`](#shardstore)
- [Updated helpers](#updated-helpers)
- [Updated `get_forkchoice_store`](#updated-get_forkchoice_store)
- [Updated `update_latest_messages`](#updated-update_latest_messages)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -20,17 +25,72 @@
This document is the beacon chain fork choice spec for part of Ethereum 2.0 Phase 1.
### Helpers
### Updated data structures
#### Extended `LatestMessage`
#### Extended `Store`
```python
@dataclass
class Store(object):
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
best_justified_checkpoint: Checkpoint
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
shard_stores: Dict[Shard, ShardStore] = field(default_factory=dict)
```
### New data structures
#### `ShardLatestMessage`
```python
@dataclass(eq=True, frozen=True)
class LatestMessage(object):
class ShardLatestMessage(object):
epoch: Epoch
root: Root
```
#### `ShardStore`
```python
@dataclass
class ShardStore:
shard: Shard
shard_root: Root
signed_blocks: Dict[Root, SignedShardBlock] = field(default_factory=dict)
block_states: Dict[Root, ShardState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, ShardLatestMessage] = field(default_factory=dict)
```
### Updated helpers
#### Updated `get_forkchoice_store`
```python
def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store:
assert anchor_block.state_root == hash_tree_root(anchor_state)
anchor_root = hash_tree_root(anchor_block)
anchor_epoch = get_current_epoch(anchor_state)
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
return Store(
time=anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot,
genesis_time=anchor_state.genesis_time,
justified_checkpoint=justified_checkpoint,
finalized_checkpoint=finalized_checkpoint,
best_justified_checkpoint=justified_checkpoint,
blocks={anchor_root: copy(anchor_block)},
block_states={anchor_root: anchor_state.copy()},
checkpoint_states={justified_checkpoint: anchor_state.copy()},
shard_stores={
Shard(shard): get_forkchoice_shard_store(anchor_state, Shard(shard))
for shard in range(get_active_shard_count(anchor_state))
}
)
```
#### Updated `update_latest_messages`
@ -43,7 +103,7 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn
shard = attestation.data.shard
for i in attesting_indices:
if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch:
store.latest_messages[i] = LatestMessage(
epoch=target.epoch, root=beacon_block_root, shard=shard, shard_root=attestation.data.shard_head_root
)
store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root)
shard_latest_message = ShardLatestMessage(epoch=target.epoch, root=attestation.data.shard_head_root)
store.shard_stores[shard].latest_messages[i] = shard_latest_message
```

View File

@ -35,18 +35,17 @@ Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
| `PHASE_1_GENESIS_SLOT` | `2**5` **TBD** |
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
| `PHASE_1_FORK_SLOT` | `Slot(0)` **TBD** |
## Fork to Phase 1
### Fork trigger
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at slot `PHASE_1_GENESIS_SLOT`, where `PHASE_1_GENESIS_SLOT % SLOTS_PER_EPOCH == 0`.
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at slot `PHASE_1_FORK_SLOT`, where `PHASE_1_FORK_SLOT % SLOTS_PER_EPOCH == 0`.
### Upgrading the state
After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_GENESIS_SLOT`, an irregular state change is made to upgrade to Phase 1.
After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_FORK_SLOT`, an irregular state change is made to upgrade to Phase 1.
```python
def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
@ -102,7 +101,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
current_epoch_start_shard=Shard(0),
shard_states=List[ShardState, MAX_SHARDS](
ShardState(
slot=pre.slot,
slot=compute_previous_slot(pre.slot),
gasprice=MIN_GASPRICE,
latest_block_root=Root(),
) for i in range(INITIAL_ACTIVE_SHARDS)

View File

@ -11,7 +11,6 @@
- [Introduction](#introduction)
- [Fork choice](#fork-choice)
- [Helpers](#helpers)
- [`ShardStore`](#shardstore)
- [`get_forkchoice_shard_store`](#get_forkchoice_shard_store)
- [`get_shard_latest_attesting_balance`](#get_shard_latest_attesting_balance)
- [`get_shard_head`](#get_shard_head)
@ -30,16 +29,6 @@ This document is the shard chain fork choice spec for part of Ethereum 2.0 Phase
### Helpers
#### `ShardStore`
```python
@dataclass
class ShardStore:
shard: Shard
signed_blocks: Dict[Root, SignedShardBlock] = field(default_factory=dict)
block_states: Dict[Root, ShardState] = field(default_factory=dict)
```
#### `get_forkchoice_shard_store`
```python
@ -48,7 +37,7 @@ def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> Shard
shard=shard,
signed_blocks={
anchor_state.shard_states[shard].latest_block_root: SignedShardBlock(
message=ShardBlock(slot=anchor_state.slot, shard=shard)
message=ShardBlock(slot=compute_previous_slot(anchor_state.slot), shard=shard)
)
},
block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]},
@ -58,18 +47,21 @@ def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> Shard
#### `get_shard_latest_attesting_balance`
```python
def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, root: Root) -> Gwei:
def get_shard_latest_attesting_balance(store: Store, shard: Shard, root: Root) -> Gwei:
shard_store = store.shard_stores[shard]
state = store.checkpoint_states[store.justified_checkpoint]
active_indices = get_active_validator_indices(state, get_current_epoch(state))
return Gwei(sum(
state.validators[i].effective_balance for i in active_indices
if (
i in store.latest_messages
i in shard_store.latest_messages
# TODO: check the latest message logic: currently, validator's previous vote of another shard
# would be ignored once their newer vote is accepted. Check if it makes sense.
and store.latest_messages[i].shard == shard_store.shard
and get_shard_ancestor(
store, shard_store, store.latest_messages[i].shard_root, shard_store.signed_blocks[root].message.slot
store,
shard,
shard_store.latest_messages[i].root,
shard_store.signed_blocks[root].message.slot,
) == root
)
))
@ -78,10 +70,14 @@ def get_shard_latest_attesting_balance(store: Store, shard_store: ShardStore, ro
#### `get_shard_head`
```python
def get_shard_head(store: Store, shard_store: ShardStore) -> Root:
def get_shard_head(store: Store, shard: Shard) -> Root:
# Execute the LMD-GHOST fork choice
"""
Execute the LMD-GHOST fork choice.
"""
shard_store = store.shard_stores[shard]
beacon_head_root = get_head(store)
shard_head_state = store.block_states[beacon_head_root].shard_states[shard_store.shard]
shard_head_state = store.block_states[beacon_head_root].shard_states[shard]
shard_head_root = shard_head_state.latest_block_root
shard_blocks = {
root: signed_shard_block.message for root, signed_shard_block in shard_store.signed_blocks.items()
@ -97,17 +93,18 @@ def get_shard_head(store: Store, shard_store: ShardStore) -> Root:
return shard_head_root
# Sort by latest attesting balance with ties broken lexicographically
shard_head_root = max(
children, key=lambda root: (get_shard_latest_attesting_balance(store, shard_store, root), root)
children, key=lambda root: (get_shard_latest_attesting_balance(store, shard, root), root)
)
```
#### `get_shard_ancestor`
```python
def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot: Slot) -> Root:
def get_shard_ancestor(store: Store, shard: Shard, root: Root, slot: Slot) -> Root:
shard_store = store.shard_stores[shard]
block = shard_store.signed_blocks[root].message
if block.slot > slot:
return get_shard_ancestor(store, shard_store, block.shard_parent_root, slot)
return get_shard_ancestor(store, shard, block.shard_parent_root, slot)
elif block.slot == slot:
return root
else:
@ -118,17 +115,17 @@ def get_shard_ancestor(store: Store, shard_store: ShardStore, root: Root, slot:
#### `get_pending_shard_blocks`
```python
def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[SignedShardBlock]:
def get_pending_shard_blocks(store: Store, shard: Shard) -> Sequence[SignedShardBlock]:
"""
Return the canonical shard block branch that has not yet been crosslinked.
"""
shard = shard_store.shard
shard_store = store.shard_stores[shard]
beacon_head_root = get_head(store)
beacon_head_state = store.block_states[beacon_head_root]
latest_shard_block_root = beacon_head_state.shard_states[shard].latest_block_root
shard_head_root = get_shard_head(store, shard_store)
shard_head_root = get_shard_head(store, shard)
root = shard_head_root
signed_shard_blocks = []
while root != latest_shard_block_root:
@ -145,13 +142,10 @@ def get_pending_shard_blocks(store: Store, shard_store: ShardStore) -> Sequence[
#### `on_shard_block`
```python
def on_shard_block(store: Store, shard_store: ShardStore, signed_shard_block: SignedShardBlock) -> None:
def on_shard_block(store: Store, signed_shard_block: SignedShardBlock) -> None:
shard_block = signed_shard_block.message
shard = shard_store.shard
# Check shard
# TODO: check it in networking spec
assert shard_block.shard == shard
shard = shard_block.shard
shard_store = store.shard_stores[shard]
# Check shard parent exists
assert shard_block.shard_parent_root in shard_store.block_states

View File

@ -31,7 +31,7 @@
- [`FullAttestation`](#fullattestation)
- [Timing](#timing)
- [Attestation data](#attestation-data)
- [Head shard root](#head-shard-root)
- [Shard head root](#shard-head-root)
- [Shard transition](#shard-transition)
- [Construct attestation](#construct-attestation)
- [Attestation Aggregation](#attestation-aggregation)
@ -267,9 +267,9 @@ A validator should create and broadcast the `attestation` to the associated atte
*Note*: We assume that the fork choice only follows branches with valid `offset_slots` with respect to the most recent beacon state shard transition for the queried shard.
##### Head shard root
##### Shard head root
Set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`.
If `attestation_data.slot == GENESIS_SLOT`, set `attestation_data.shard_head_root = Root()`. Otherwise, set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`.
##### Shard transition
@ -281,9 +281,9 @@ def get_shard_transition_fields(
shard: Shard,
shard_blocks: Sequence[SignedShardBlock],
) -> Tuple[Sequence[uint64], Sequence[Root], Sequence[ShardState]]:
shard_states = []
shard_data_roots = []
shard_block_lengths = []
shard_block_lengths = [] # type: PyList[uint64]
shard_data_roots = [] # type: PyList[Root]
shard_states = [] # type: PyList[ShardState]
shard_state = beacon_state.shard_states[shard]
shard_block_slots = [shard_block.message.slot for shard_block in shard_blocks]
@ -294,14 +294,14 @@ def get_shard_transition_fields(
for slot in offset_slots:
if slot in shard_block_slots:
shard_block = shard_blocks[shard_block_slots.index(slot)]
shard_data_roots.append(get_block_data_merkle_root(shard_block.message.body))
shard_data_roots.append(hash_tree_root(shard_block.message.body))
else:
shard_block = SignedShardBlock(message=ShardBlock(slot=slot, shard=shard))
shard_data_roots.append(Root())
shard_state = shard_state.copy()
process_shard_block(shard_state, shard_block.message)
shard_states.append(shard_state)
shard_block_lengths.append(len(shard_block.message.body))
shard_block_lengths.append(uint64(len(shard_block.message.body)))
return shard_block_lengths, shard_data_roots, shard_states
```
@ -310,6 +310,10 @@ def get_shard_transition_fields(
def get_shard_transition(beacon_state: BeaconState,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition:
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if beacon_state.slot == GENESIS_SLOT:
return ShardTransition()
offset_slots = compute_offset_slots(
get_latest_slot_for_shard(beacon_state, shard),
Slot(beacon_state.slot + 1),

View File

@ -165,7 +165,7 @@ variable_lengths = [len(part) for part in variable_parts]
assert sum(fixed_lengths + variable_lengths) < 2**(BYTES_PER_LENGTH_OFFSET * BITS_PER_BYTE)
# Interleave offsets of variable-size parts with fixed-size parts
variable_offsets = [serialize(sum(fixed_lengths + variable_lengths[:i])) for i in range(len(value))]
variable_offsets = [serialize(uint32(sum(fixed_lengths + variable_lengths[:i]))) for i in range(len(value))]
fixed_parts = [part if part != None else variable_offsets[i] for i, part in enumerate(fixed_parts)]
# Return the concatenation of the fixed-size parts (offsets interleaved) with the variable-size parts

View File

@ -2,6 +2,7 @@ import argparse
from pathlib import Path
import sys
from typing import Iterable, AnyStr, Any, Callable
import traceback
from ruamel.yaml import (
YAML,
@ -9,6 +10,13 @@ from ruamel.yaml import (
from gen_base.gen_typing import TestProvider
from eth2spec.test import context
from eth2spec.test.exceptions import SkippedTest
# Flag that the runner does NOT run test via pytest
context.is_pytest = False
def validate_output_dir(path_str):
path = Path(path_str)
@ -134,14 +142,20 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
written_part = False
meta = dict()
for (name, out_kind, data) in test_case.case_fn():
written_part = True
if out_kind == "meta":
meta[name] = data
if out_kind == "data":
output_part("data", name, dump_yaml_fn(data, name, file_mode, yaml))
if out_kind == "ssz":
output_part("ssz", name, dump_ssz_fn(data, name, file_mode))
try:
for (name, out_kind, data) in test_case.case_fn():
written_part = True
if out_kind == "meta":
meta[name] = data
if out_kind == "data":
output_part("data", name, dump_yaml_fn(data, name, file_mode, yaml))
if out_kind == "ssz":
output_part("ssz", name, dump_ssz_fn(data, name, file_mode))
except SkippedTest as e:
print(e)
continue
# Once all meta data is collected (if any), write it to a meta data file.
if len(meta) != 0:
written_part = True
@ -152,6 +166,7 @@ def run_generator(generator_name, test_providers: Iterable[TestProvider]):
except Exception as e:
print(f"ERROR: failed to generate vector(s) for test {case_dir}: {e}")
traceback.print_exc()
print(f"completed {generator_name}")

View File

@ -1,2 +1,3 @@
ruamel.yaml==0.16.5
eth-utils==1.6.0
pytest>=4.4

View File

@ -5,6 +5,7 @@ setup(
packages=['gen_base', 'gen_from_tests'],
install_requires=[
"ruamel.yaml==0.16.5",
"eth-utils==1.6.0"
"eth-utils==1.6.0",
"pytest>=4.4",
]
)

View File

@ -1 +1 @@
0.12.2
0.12.3

View File

@ -54,6 +54,8 @@ def load_config_file(configs_dir: str, presets_name: str) -> Dict[str, Any]:
out[k] = [int(item) if item.isdigit() else item for item in v]
elif isinstance(v, str) and v.startswith("0x"):
out[k] = bytes.fromhex(v[2:])
elif k == "CONFIG_NAME":
out[k] = str(v)
else:
out[k] = int(v)
return out

View File

@ -68,9 +68,8 @@ def get_random_ssz_object(rng: Random,
else:
return typ(get_random_bytes_list(rng, rng.randint(0, min(max_bytes_length, typ.limit()))))
if issubclass(typ, ByteVector):
# Sanity, don't generate absurdly big random values
# If a client is aiming to performance-test, they should create a benchmark suite.
assert typ.type_byte_length() <= max_bytes_length
# Random byte vectors can be bigger than max bytes size, e.g. custody chunk data.
# No max-bytes-length limitation here.
if mode == RandomizationMode.mode_zero:
return typ(b'\x00' * typ.type_byte_length())
elif mode == RandomizationMode.mode_max:

View File

@ -31,7 +31,7 @@ def pytest_addoption(parser):
help="config: make the pyspec use the specified configuration"
)
parser.addoption(
"--disable-bls", action="store_true",
"--disable-bls", action="store_true", default=False,
help="bls-default: make tests that are not dependent on BLS run without BLS"
)
parser.addoption(

View File

@ -1,9 +1,11 @@
import pytest
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.utils import bls
from .exceptions import SkippedTest
from .helpers.genesis import create_genesis_state
from .utils import vector_test, with_meta_tags
from random import Random
@ -22,11 +24,16 @@ def reload_specs():
# Some of the Spec module functionality is exposed here to deal with phase-specific changes.
SpecForkName = NewType("SpecForkName", str)
ConfigName = NewType("ConfigName", str)
PHASE0 = SpecForkName('phase0')
PHASE1 = SpecForkName('phase1')
ALL_PHASES = (PHASE0, PHASE1)
MAINNET = ConfigName('mainnet')
MINIMAL = ConfigName('minimal')
# TODO: currently phases are defined as python modules.
# It would be better if they would be more well-defined interfaces for stronger typing.
@ -63,9 +70,6 @@ def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Ca
# TODO: instead of upgrading a test phase0 genesis state we can also write a phase1 state helper.
# Decide based on performance/consistency results later.
state = phases[PHASE1].upgrade_to_phase1(state)
# Shard state slot must lag behind BeaconState slot by at least 1
# Will handle this more elegantly with fork mechanics
spec.process_slots(state, state.slot + 1)
return state
@ -154,6 +158,15 @@ def low_single_balance(spec):
return [1]
def large_validator_set(spec):
"""
Helper method to create a large series of default balances.
Usage: `@with_custom_state(balances_fn=default_balances, ...)`
"""
num_validators = 2 * spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
def single_phase(fn):
"""
Decorator that filters out the phases data.
@ -178,6 +191,17 @@ def single_phase(fn):
DEFAULT_BLS_ACTIVE = True
is_pytest = True
def dump_skipping_message(reason: str) -> None:
message = f"[Skipped test] {reason}"
if is_pytest:
pytest.skip(message)
else:
raise SkippedTest(message)
def spec_test(fn):
# Bls switch must be wrapped by vector_test,
# to fully go through the yielded bls switch data, before setting back the BLS setting.
@ -249,6 +273,24 @@ def bls_switch(fn):
return entry
def disable_process_reveal_deadlines(fn):
"""
Decorator to make a function execute with `process_reveal_deadlines` OFF.
This is for testing long-range epochs transition without considering the reveal-deadline slashing effect.
"""
def entry(*args, spec: Spec, **kw):
if hasattr(spec, 'process_reveal_deadlines'):
old_state = spec.process_reveal_deadlines
spec.process_reveal_deadlines = lambda state: None
yield from fn(*args, spec=spec, **kw)
if hasattr(spec, 'process_reveal_deadlines'):
spec.process_reveal_deadlines = old_state
return with_meta_tags({'reveal_deadlines_setting': 1})(entry)
def with_all_phases(fn):
"""
A decorator for running a test with every phase
@ -278,7 +320,8 @@ def with_phases(phases, other_phases=None):
if 'phase' in kw:
phase = kw.pop('phase')
if phase not in phases:
return
dump_skipping_message(f"doesn't support this fork: {phase}")
return None
run_phases = [phase]
available_phases = set(run_phases)
@ -303,3 +346,33 @@ def with_phases(phases, other_phases=None):
return ret
return wrapper
return decorator
def with_configs(configs, reason=None):
def decorator(fn):
def wrapper(*args, spec: Spec, **kw):
available_configs = set(configs)
if spec.CONFIG_NAME not in available_configs:
message = f"doesn't support this config: {spec.CONFIG_NAME}."
if reason is not None:
message = f"{message} Reason: {reason}"
dump_skipping_message(message)
return None
return fn(*args, spec=spec, **kw)
return wrapper
return decorator
def only_full_crosslink(fn):
def is_full_crosslink(spec, state):
epoch = spec.compute_epoch_at_slot(state.slot)
return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state)
def wrapper(*args, spec: Spec, state: Any, **kw):
# TODO: update condition to "phase1+" if we have phase2
if spec.fork == PHASE1 and not is_full_crosslink(spec, state):
dump_skipping_message("only for full crosslink")
return None
return fn(*args, spec=spec, state=state, **kw)
return wrapper

View File

@ -0,0 +1,2 @@
class SkippedTest(Exception):
...

View File

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

View File

@ -113,7 +113,7 @@ def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_i
def custody_chunkify(spec, x):
chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)]
chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0")
return chunks
return [ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](c) for c in chunks]
def build_proof(anchor, leaf_index):
@ -149,12 +149,14 @@ def get_valid_custody_chunk_response(spec, state, chunk_challenge, challenge_ind
chunk_index = chunk_challenge.chunk_index
data_branch = build_proof(custody_data_block.get_backing().get_left(), chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH)
leaf_index = chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH
serialized_length = len(custody_data_block).to_bytes(32, 'little')
data_branch = build_proof(custody_data_block.get_backing().get_left(), leaf_index) + [serialized_length]
return spec.CustodyChunkResponse(
challenge_index=challenge_index,
chunk_index=chunk_index,
chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
chunk=chunks[chunk_index],
branch=data_branch,
)
@ -165,7 +167,7 @@ def get_custody_test_vector(bytelength, offset=0):
def get_sample_shard_transition(spec, start_slot, block_lengths):
b = [spec.get_block_data_merkle_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)))
b = [spec.hash_tree_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x)))
for x in block_lengths]
shard_transition = spec.ShardTransition(
start_slot=start_slot,
@ -177,15 +179,6 @@ def get_sample_shard_transition(spec, start_slot, block_lengths):
return shard_transition
def get_custody_secret(spec, state, validator_index, epoch=None):
period = spec.get_custody_period_for_validator(validator_index, epoch if epoch is not None
else spec.get_current_epoch(state))
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, validator_index)
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
return bls.Sign(privkeys[validator_index], signing_root)
def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True):
test_vector = get_custody_test_vector(length)
offset = 0
@ -200,5 +193,5 @@ def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, cust
slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret,
block_lengths[0], slashable=slashable)
block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector)
shard_transition.shard_data_roots[0] = spec.get_block_data_merkle_root(block_data)
shard_transition.shard_data_roots[0] = spec.hash_tree_root(block_data)
return shard_transition, slashable_test_vector

View File

@ -1,3 +1,6 @@
from eth2spec.phase0 import spec as phase0_spec
def get_anchor_root(spec, state):
anchor_block_header = state.latest_block_header.copy()
if anchor_block_header.state_root == spec.Bytes32():
@ -25,3 +28,10 @@ def add_attestation_to_store(spec, store, attestation):
spec.on_tick(store, next_epoch_time)
spec.on_attestation(store, attestation)
def get_genesis_forkchoice_store(spec, genesis_state):
assert genesis_state.slot == spec.GENESIS_SLOT
# The genesis block must be a Phase 0 `BeaconBlock`
genesis_block = phase0_spec.BeaconBlock(state_root=genesis_state.hash_tree_root())
return spec.get_forkchoice_store(genesis_state, genesis_block)

View File

@ -35,8 +35,3 @@ def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks
shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot)
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks)
return shard_transition
def is_full_crosslink(spec, state):
epoch = spec.compute_epoch_at_slot(state.slot)
return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state)

View File

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

View File

@ -1,6 +1,5 @@
from eth2spec.test.context import (
PHASE0,
spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
spec_state_test, expect_assertion_error, always_bls, with_all_phases
)
from eth2spec.test.helpers.attestations import sign_indexed_attestation
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \
@ -194,10 +193,7 @@ def test_participants_already_slashed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
# Some of the following tests are phase0 only: phase 1 lists participants with bitfields instead of index list.
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_high_index(spec, state):
@ -210,7 +206,7 @@ def test_att1_high_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_high_index(spec, state):
@ -223,7 +219,7 @@ def test_att2_high_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_empty_indices(spec, state):
@ -235,7 +231,7 @@ def test_att1_empty_indices(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_empty_indices(spec, state):
@ -247,7 +243,7 @@ def test_att2_empty_indices(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_all_empty_indices(spec, state):
@ -262,7 +258,7 @@ def test_all_empty_indices(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_bad_extra_index(spec, state):
@ -278,7 +274,7 @@ def test_att1_bad_extra_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_bad_replaced_index(spec, state):
@ -294,7 +290,7 @@ def test_att1_bad_replaced_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_bad_extra_index(spec, state):
@ -310,7 +306,7 @@ def test_att2_bad_extra_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_bad_replaced_index(spec, state):
@ -326,7 +322,7 @@ def test_att2_bad_replaced_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_duplicate_index_normal_signed(spec, state):
@ -346,7 +342,7 @@ def test_att1_duplicate_index_normal_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_duplicate_index_normal_signed(spec, state):
@ -366,7 +362,7 @@ def test_att2_duplicate_index_normal_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_duplicate_index_double_signed(spec, state):
@ -381,7 +377,7 @@ def test_att1_duplicate_index_double_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_duplicate_index_double_signed(spec, state):
@ -396,7 +392,7 @@ def test_att2_duplicate_index_double_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_unsorted_att_1(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
@ -409,7 +405,7 @@ def test_unsorted_att_1(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_unsorted_att_2(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)

View File

@ -79,6 +79,20 @@ def test_invalid_sig_1_and_2(spec, state):
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_sig_1_and_2_swap(spec, state):
# Get valid signatures for the slashings
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
# But swap them
signature_1 = proposer_slashing.signed_header_1.signature
proposer_slashing.signed_header_1.signature = proposer_slashing.signed_header_2.signature
proposer_slashing.signed_header_2.signature = signature_1
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@with_all_phases
@spec_state_test
def test_invalid_proposer_index(spec, state):
@ -122,11 +136,26 @@ def test_epochs_are_different(spec, state):
@with_all_phases
@spec_state_test
def test_headers_are_same(spec, state):
def test_headers_are_same_sigs_are_same(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False)
# set headers to be the same
proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1
proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1.copy()
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)
@with_all_phases
@spec_state_test
def test_headers_are_same_sigs_are_different(spec, state):
proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False)
# set headers to be the same
proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1.copy()
# but signatures to be different
proposer_slashing.signed_header_2.signature = proposer_slashing.signed_header_2.signature[:-1] + b'\x00'
assert proposer_slashing.signed_header_1.signature != proposer_slashing.signed_header_2.signature
yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False)

View File

@ -1,7 +1,6 @@
from eth2spec.test.context import (
PHASE0,
spec_state_test, spec_test,
with_all_phases, with_phases, single_phase,
with_all_phases, single_phase,
with_custom_state,
zero_activation_threshold,
misc_balances, low_single_balance,
@ -25,7 +24,7 @@ def run_process_rewards_and_penalties(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties')
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_genesis_epoch_no_attestations_no_penalties(spec, state):
pre_state = state.copy()
@ -38,7 +37,7 @@ def test_genesis_epoch_no_attestations_no_penalties(spec, state):
assert state.balances[index] == pre_state.balances[index]
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_genesis_epoch_full_attestations_no_rewards(spec, state):
attestations = []

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases, with_phases
from eth2spec.test.context import spec_state_test, with_all_phases
from eth2spec.test.helpers.state import next_epoch_via_block
from eth2spec.test.helpers.attestations import next_epoch_with_attestations
@ -28,7 +28,7 @@ def check_finality(spec,
assert state.finalized_checkpoint == prev_state.finalized_checkpoint
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_finality_no_updates_at_genesis(spec, state):
assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH

View File

@ -21,8 +21,14 @@ from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
from eth2spec.test.context import (
PHASE0, PHASE1,
spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases,
PHASE0, PHASE1, MINIMAL,
spec_test, spec_state_test, dump_skipping_message,
with_phases, with_all_phases, single_phase,
expect_assertion_error, always_bls,
disable_process_reveal_deadlines,
with_configs,
with_custom_state,
large_validator_set,
)
@ -70,6 +76,7 @@ def test_same_slot_block_transition(spec, state):
def test_empty_block_transition(spec, state):
pre_slot = state.slot
pre_eth1_votes = len(state.eth1_data_votes)
pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state))
yield 'pre', state
@ -82,7 +89,32 @@ def test_empty_block_transition(spec, state):
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32()
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != pre_mix
@with_all_phases
@with_configs([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.EJECTION_BALANCE)
@single_phase
def test_empty_block_transition_large_validator_set(spec, state):
pre_slot = state.slot
pre_eth1_votes = len(state.eth1_data_votes)
pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state))
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != pre_mix
def process_and_sign_block_without_header_validations(spec, state, block):
@ -288,13 +320,34 @@ def test_empty_epoch_transition(spec, state):
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
@with_all_phases
@with_configs([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.EJECTION_BALANCE)
@single_phase
def test_empty_epoch_transition_large_validator_set(spec, state):
pre_slot = state.slot
yield 'pre', state
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert state.slot == block.slot
for slot in range(pre_slot, state.slot):
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):
# 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
return dump_skipping_message("Skip mainnet config for saving time."
" Minimal config suffice to cover the target-of-test.")
# copy for later balance lookups.
pre_balances = list(state.balances)
@ -480,9 +533,8 @@ def test_attester_slashing(spec, state):
@with_all_phases
@spec_state_test
def test_duplicate_attester_slashing(spec, state):
# Skip test if config cannot handle multiple AttesterSlashings per block
if spec.MAX_ATTESTER_SLASHINGS < 2:
return
return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block")
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True)
attester_slashings = [attester_slashing, attester_slashing.copy()]
@ -506,12 +558,11 @@ def test_duplicate_attester_slashing(spec, state):
# All AttesterSlashing tests should be adopted for Phase 1 but helper support is not yet there
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_multiple_attester_slashings_no_overlap(spec, state):
# Skip test if config cannot handle multiple AttesterSlashings per block
if spec.MAX_ATTESTER_SLASHINGS < 2:
return
return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block")
# copy for later balance lookups.
pre_state = state.copy()
@ -547,12 +598,11 @@ def test_multiple_attester_slashings_no_overlap(spec, state):
check_attester_slashing_effect(spec, pre_state, state, full_indices)
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_multiple_attester_slashings_partial_overlap(spec, state):
# Skip test if config cannot handle multiple AttesterSlashings per block
if spec.MAX_ATTESTER_SLASHINGS < 2:
return
return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block")
# copy for later balance lookups.
pre_state = state.copy()
@ -762,8 +812,9 @@ def prepare_signed_exits(spec, state, indices):
# exceeding the minimal-config randao mixes memory size.
# Applies to all voluntary-exit sanity block tests.
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@disable_process_reveal_deadlines
def test_voluntary_exit(spec, state):
validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
@ -790,7 +841,7 @@ def test_voluntary_exit(spec, state):
assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_double_validator_exit_same_block(spec, state):
validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
@ -811,8 +862,9 @@ def test_double_validator_exit_same_block(spec, state):
yield 'post', None
@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@disable_process_reveal_deadlines
def test_multiple_different_validator_exits_same_block(spec, state):
validator_indices = [
spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i]
@ -888,9 +940,9 @@ def test_historical_batch(spec, 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.EPOCHS_PER_ETH1_VOTING_PERIOD > 2:
return
return dump_skipping_message("Skip test if config with longer `EPOCHS_PER_ETH1_VOTING_PERIOD` for saving time."
" Minimal config suffice to cover the target-of-test.")
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH
@ -932,9 +984,9 @@ def test_eth1_data_votes_consensus(spec, state):
@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.EPOCHS_PER_ETH1_VOTING_PERIOD > 2:
return
return dump_skipping_message("Skip test if config with longer `EPOCHS_PER_ETH1_VOTING_PERIOD` for saving time."
" Minimal config suffice to cover the target-of-test.")
voting_period_slots = spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH

View File

@ -1,7 +1,11 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.fork_choice import add_attestation_to_store, add_block_to_store, get_anchor_root
from eth2spec.test.helpers.fork_choice import (
add_attestation_to_store,
add_block_to_store, get_anchor_root,
get_genesis_forkchoice_store,
)
from eth2spec.test.helpers.state import (
next_epoch,
state_transition_and_sign_block,
@ -12,7 +16,7 @@ from eth2spec.test.helpers.state import (
@spec_state_test
def test_genesis(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
@ -21,7 +25,7 @@ def test_genesis(spec, state):
@spec_state_test
def test_chain_no_attestations(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
@ -44,7 +48,7 @@ def test_split_tie_breaker_no_attestations(spec, state):
genesis_state = state.copy()
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
@ -72,13 +76,13 @@ def test_shorter_chain_but_heavier_weight(spec, state):
genesis_state = state.copy()
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
# build longer tree
long_state = genesis_state.copy()
for i in range(3):
for _ in range(3):
long_block = build_empty_block_for_next_slot(spec, long_state)
signed_long_block = state_transition_and_sign_block(spec, long_state, long_block)
add_block_to_store(spec, store, signed_long_block)
@ -100,7 +104,7 @@ def test_shorter_chain_but_heavier_weight(spec, state):
@spec_state_test
def test_filtered_block_tree(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
anchor_root = get_anchor_root(spec, state)
# transition state past initial couple of epochs

View File

@ -2,6 +2,7 @@ from eth2spec.test.context import PHASE0, with_all_phases, spec_state_test
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation
from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
def run_on_attestation(spec, state, store, attestation, valid=True):
@ -26,9 +27,12 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,
shard=attestation.data.shard,
shard_root=attestation.data.shard_head_root,
)
shard_latest_message = spec.ShardLatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.shard_head_root,
)
assert store.shard_stores[attestation.data.shard].latest_messages[sample_index] == shard_latest_message
assert (
store.latest_messages[sample_index] == latest_message
@ -38,7 +42,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
@with_all_phases
@spec_state_test
def test_on_attestation_current_epoch(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * 2)
block = build_empty_block_for_next_slot(spec, state)
@ -57,7 +61,7 @@ def test_on_attestation_current_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_previous_epoch(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH)
block = build_empty_block_for_next_slot(spec, state)
@ -76,7 +80,7 @@ def test_on_attestation_previous_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_past_epoch(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
# move time forward 2 epochs
time = store.time + 2 * spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
@ -98,7 +102,7 @@ def test_on_attestation_past_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_mismatched_target_and_slot(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
spec.on_tick(store, store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH)
block = build_empty_block_for_next_slot(spec, state)
@ -121,7 +125,7 @@ def test_on_attestation_mismatched_target_and_slot(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_inconsistent_target_and_head(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
spec.on_tick(store, store.time + 2 * spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH)
# Create chain 1 as empty chain between genesis and start of 1st epoch
@ -159,7 +163,7 @@ def test_on_attestation_inconsistent_target_and_head(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_target_block_not_in_store(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
spec.on_tick(store, time)
@ -181,7 +185,7 @@ def test_on_attestation_target_block_not_in_store(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_target_checkpoint_not_in_store(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
spec.on_tick(store, time)
@ -206,7 +210,7 @@ def test_on_attestation_target_checkpoint_not_in_store(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_target_checkpoint_not_in_store_diff_slot(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
spec.on_tick(store, time)
@ -233,7 +237,7 @@ def test_on_attestation_target_checkpoint_not_in_store_diff_slot(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_beacon_block_not_in_store(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + spec.SECONDS_PER_SLOT * (spec.SLOTS_PER_EPOCH + 1)
spec.on_tick(store, time)
@ -262,7 +266,7 @@ def test_on_attestation_beacon_block_not_in_store(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_future_epoch(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + 3 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
@ -282,7 +286,7 @@ def test_on_attestation_future_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_future_block(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + spec.SECONDS_PER_SLOT * 5
spec.on_tick(store, time)
@ -302,7 +306,7 @@ def test_on_attestation_future_block(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_same_slot(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + spec.SECONDS_PER_SLOT
spec.on_tick(store, time)
@ -318,7 +322,7 @@ def test_on_attestation_same_slot(spec, state):
@with_all_phases
@spec_state_test
def test_on_attestation_invalid_attestation(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = store.time + 3 * spec.SECONDS_PER_SLOT
spec.on_tick(store, time)

View File

@ -2,10 +2,11 @@ from copy import deepcopy
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.attestations import next_epoch_with_attestations
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block, transition_unsigned_block, \
build_empty_block
from eth2spec.test.helpers.attestations import next_epoch_with_attestations
from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
from eth2spec.test.helpers.state import next_epoch, state_transition_and_sign_block, transition_to
def run_on_block(spec, store, signed_block, valid=True):
@ -37,7 +38,7 @@ def apply_next_epoch_with_attestations(spec, state, store):
@spec_state_test
def test_basic(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 100
spec.on_tick(store, time)
assert store.time == time
@ -61,7 +62,7 @@ def test_basic(spec, state):
@spec_state_test
def test_on_block_checkpoints(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 100
spec.on_tick(store, time)
@ -87,7 +88,7 @@ def test_on_block_checkpoints(spec, state):
@spec_state_test
def test_on_block_future_block(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
# do not tick time
@ -101,7 +102,7 @@ def test_on_block_future_block(spec, state):
@spec_state_test
def test_on_block_bad_parent_root(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 100
spec.on_tick(store, time)
@ -121,7 +122,7 @@ def test_on_block_bad_parent_root(spec, state):
@spec_state_test
def test_on_block_before_finalized(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 100
spec.on_tick(store, time)
@ -140,7 +141,7 @@ def test_on_block_before_finalized(spec, state):
@spec_state_test
def test_on_block_finalized_skip_slots(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 100
spec.on_tick(store, time)
@ -160,16 +161,18 @@ def test_on_block_finalized_skip_slots(spec, state):
@spec_state_test
def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state):
# Initialization
next_epoch(spec, state)
store = spec.get_forkchoice_store(state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH - 1)
block = build_empty_block_for_next_slot(spec, state)
transition_unsigned_block(spec, state, block)
block.state_root = state.hash_tree_root()
store = spec.get_forkchoice_store(state, block)
store.finalized_checkpoint = spec.Checkpoint(
epoch=store.finalized_checkpoint.epoch + 2,
root=store.finalized_checkpoint.root
)
# First transition through the epoch to ensure no skipped slots
state, store, last_signed_block = apply_next_epoch_with_attestations(spec, state, store)
state, store, _ = apply_next_epoch_with_attestations(spec, state, store)
# Now build a block at later slot than finalized epoch
# Includes finalized block in chain, but not at appropriate skip slot
@ -183,7 +186,7 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state):
@spec_state_test
def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 0
spec.on_tick(store, time)
@ -214,7 +217,7 @@ def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state):
@spec_state_test
def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 0
spec.on_tick(store, time)
@ -264,7 +267,7 @@ def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state):
@spec_state_test
def test_on_block_outside_safe_slots_but_finality(spec, state):
# Initialization
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
time = 100
spec.on_tick(store, time)

View File

@ -1,4 +1,5 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
def run_on_tick(spec, store, time, new_justified_checkpoint=False):
@ -19,14 +20,14 @@ def run_on_tick(spec, store, time, new_justified_checkpoint=False):
@with_all_phases
@spec_state_test
def test_basic(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
run_on_tick(spec, store, store.time + 1)
@with_all_phases
@spec_state_test
def test_update_justified_single(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
next_epoch = spec.get_current_epoch(state) + 1
next_epoch_start_slot = spec.compute_start_slot_at_epoch(next_epoch)
seconds_until_next_epoch = next_epoch_start_slot * spec.SECONDS_PER_SLOT - store.time
@ -42,7 +43,7 @@ def test_update_justified_single(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_same_slot_at_epoch_boundary(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
@ -59,7 +60,7 @@ def test_no_update_same_slot_at_epoch_boundary(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_not_epoch_boundary(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
store.best_justified_checkpoint = spec.Checkpoint(
epoch=store.justified_checkpoint.epoch + 1,
@ -72,7 +73,7 @@ def test_no_update_not_epoch_boundary(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_new_justified_equal_epoch(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(
@ -91,7 +92,7 @@ def test_no_update_new_justified_equal_epoch(spec, state):
@with_all_phases
@spec_state_test
def test_no_update_new_justified_later_epoch(spec, state):
store = spec.get_forkchoice_store(state)
store = get_genesis_forkchoice_store(spec, state)
seconds_per_epoch = spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
store.best_justified_checkpoint = spec.Checkpoint(

View File

@ -6,12 +6,15 @@ from eth2spec.test.helpers.custody import (
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
PHASE0,
with_all_phases_except,
spec_state_test,
MINIMAL,
expect_assertion_error,
disable_process_reveal_deadlines,
spec_state_test,
with_all_phases_except,
with_configs,
)
from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
@ -67,8 +70,11 @@ def run_custody_chunk_response_processing(spec, state, custody_response, valid=T
@with_all_phases_except([PHASE0])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
@disable_process_reveal_deadlines
def test_challenge_appended(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -88,8 +94,11 @@ def test_challenge_appended(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_challenge_empty_element_replaced(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -111,8 +120,11 @@ def test_challenge_empty_element_replaced(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_duplicate_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -134,8 +146,11 @@ def test_duplicate_challenge(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_second_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
@ -159,7 +174,10 @@ def test_second_challenge(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_multiple_epochs_custody(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
shard = 0
@ -181,7 +199,10 @@ def test_multiple_epochs_custody(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_many_epochs_custody(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
shard = 0
@ -203,7 +224,10 @@ def test_many_epochs_custody(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_off_chain_attestation(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
@ -221,7 +245,10 @@ def test_off_chain_attestation(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_custody_response(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
@ -250,7 +277,41 @@ def test_custody_response(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_custody_response_chunk_index_2(spec, state):
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))
attestation = get_valid_on_time_attestation(spec, state, index=shard, signed=True,
shard_transition=shard_transition)
transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
_, _, _ = run_attestation_processing(spec, state, attestation)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1))
challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=2)
_, _, _ = run_chunk_challenge_processing(spec, state, challenge)
chunk_challenge_index = state.custody_chunk_challenge_index - 1
custody_response = get_valid_custody_chunk_response(
spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3)
yield from run_custody_chunk_response_processing(spec, state, custody_response)
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_custody_response_multiple_epochs(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3)
shard = 0
@ -279,7 +340,10 @@ def test_custody_response_multiple_epochs(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_custody_response_many_epochs(spec, state):
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20)
shard = 0

View File

@ -1,18 +1,21 @@
from eth2spec.test.helpers.custody import (
get_valid_custody_slashing,
get_custody_secret,
get_custody_slashable_shard_transition,
)
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.ssz.ssz_typing import ByteList
from eth2spec.test.helpers.state import get_balance, transition_to
from eth2spec.test.context import (
PHASE0,
MINIMAL,
with_all_phases_except,
spec_state_test,
expect_assertion_error,
disable_process_reveal_deadlines,
with_configs,
)
from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
@ -63,6 +66,7 @@ def run_standard_custody_slashing_test(spec,
slashing_message_data=None,
correct=True,
valid=True):
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
if shard_lateness is None:
shard_lateness = spec.SLOTS_PER_EPOCH
transition_to(spec, state, state.slot + shard_lateness)
@ -76,7 +80,12 @@ def run_standard_custody_slashing_test(spec,
if block_lengths is None:
block_lengths = [2**15 // 3] * len(offset_slots)
custody_secret = get_custody_secret(spec, state, validator_index)
custody_secret = spec.get_custody_secret(
state,
validator_index,
privkeys[validator_index],
spec.get_current_epoch(state),
)
shard_transition, slashable_test_vector = get_custody_slashable_shard_transition(
spec,
state.slot,
@ -105,30 +114,40 @@ def run_standard_custody_slashing_test(spec,
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_custody_slashing(spec, state):
yield from run_standard_custody_slashing_test(spec, state)
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_incorrect_custody_slashing(spec, state):
yield from run_standard_custody_slashing_test(spec, state, correct=False)
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_multiple_epochs_custody(spec, state):
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3)
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_many_epochs_custody(spec, state):
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 10)
yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 5)
@with_all_phases_except([PHASE0])
@spec_state_test
@disable_process_reveal_deadlines
@with_configs([MINIMAL], reason="too slow")
def test_invalid_custody_slashing(spec, state):
yield from run_standard_custody_slashing_test(
spec,

View File

@ -1,6 +1,7 @@
from eth2spec.test.context import (
PHASE0,
with_all_phases_except,
only_full_crosslink,
spec_state_test,
)
from eth2spec.test.helpers.attestations import (
@ -10,7 +11,6 @@ from eth2spec.test.helpers.attestations import (
)
from eth2spec.test.helpers.shard_transitions import (
run_shard_transitions_processing,
is_full_crosslink,
)
from eth2spec.test.helpers.shard_block import (
build_shard_block,
@ -22,7 +22,7 @@ from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard
def get_initial_env(spec, state, target_len_offset_slot):
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
committee_index = spec.CommitteeIndex(0)
target_shard_slot = state.slot + target_len_offset_slot - 1
shard = spec.compute_shard_from_committee_index(state, committee_index, target_shard_slot)
@ -92,31 +92,22 @@ def run_successful_crosslink_tests(spec, state, target_len_offset_slot):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_basic_crosslinks(spec, state):
if not is_full_crosslink(spec, state):
# Skip this test
return
yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=1)
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_multiple_offset_slots(spec, state):
if not is_full_crosslink(spec, state):
# Skip this test
return
yield from run_successful_crosslink_tests(spec, state, target_len_offset_slot=2)
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_no_winning_root(spec, state):
if not is_full_crosslink(spec, state):
# Skip this test
return
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
init_slot = state.slot
@ -163,11 +154,8 @@ def test_no_winning_root(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_wrong_shard_transition_root(spec, state):
if not is_full_crosslink(spec, state):
# Skip this test
return
state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot=1)
init_slot = state.slot

View File

@ -5,11 +5,13 @@ from eth2spec.test.helpers.custody import (
from eth2spec.test.helpers.attestations import (
get_valid_on_time_attestation,
)
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot
from eth2spec.test.context import (
PHASE0,
with_all_phases_except,
MINIMAL,
spec_state_test,
with_all_phases_except,
with_configs,
)
from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing
from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
@ -25,8 +27,10 @@ def run_process_challenge_deadlines(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_validator_slashed_after_chunk_challenge(spec, state):
transition_to(spec, state, state.slot + 1)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1
shard = 0
offset_slots = spec.get_offset_slots(state, shard)
shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots))

View File

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

View File

@ -4,7 +4,9 @@ from eth2spec.test.helpers.custody import (
from eth2spec.test.helpers.state import transition_to
from eth2spec.test.context import (
PHASE0,
MINIMAL,
with_all_phases_except,
with_configs,
spec_state_test,
)
from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
@ -17,6 +19,7 @@ def run_process_challenge_deadlines(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_validator_slashed_after_reveal_deadline(spec, state):
assert state.validators[0].slashed == 0
transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH)
@ -36,6 +39,7 @@ def test_validator_slashed_after_reveal_deadline(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@with_configs([MINIMAL], reason="too slow")
def test_validator_not_slashed_after_reveal(spec, state):
transition_to(spec, state, spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
custody_key_reveal = get_valid_custody_key_reveal(spec, state)

View File

@ -1,14 +1,15 @@
from typing import Dict, Sequence
from eth2spec.test.context import (
PHASE0,
PHASE0, MINIMAL,
with_all_phases_except,
spec_state_test,
only_full_crosslink,
with_configs,
)
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
from eth2spec.test.helpers.block import build_empty_block
from eth2spec.test.helpers.custody import (
get_custody_secret,
get_custody_slashable_test_vector,
get_valid_chunk_challenge,
get_valid_custody_chunk_response,
@ -16,13 +17,13 @@ from eth2spec.test.helpers.custody import (
get_valid_custody_slashing,
get_valid_early_derived_secret_reveal,
)
from eth2spec.test.helpers.keys import privkeys
from eth2spec.test.helpers.shard_block import (
build_shard_block,
get_committee_index_of_shard,
get_sample_shard_block_body,
get_shard_transitions,
)
from eth2spec.test.helpers.shard_transitions import is_full_crosslink
from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to
@ -99,13 +100,9 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_process_beacon_block_with_normal_shard_transition(spec, state):
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
if not is_full_crosslink(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
committee_index = spec.CommitteeIndex(0)
@ -117,13 +114,9 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_process_beacon_block_with_empty_proposal_transition(spec, state):
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
if not is_full_crosslink(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
target_len_offset_slot = 1
committee_index = spec.CommitteeIndex(0)
@ -140,13 +133,9 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
if not is_full_crosslink(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
# build shard block
shard = 0
@ -178,8 +167,9 @@ def test_with_shard_transition_with_custody_challenge_and_response(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@with_configs([MINIMAL])
def test_custody_key_reveal(spec, state):
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH)
block = build_empty_block(spec, state, slot=state.slot + 1)
@ -192,7 +182,7 @@ def test_custody_key_reveal(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
def test_early_derived_secret_reveal(spec, state):
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
block = build_empty_block(spec, state, slot=state.slot + 1)
early_derived_secret_reveal = get_valid_early_derived_secret_reveal(spec, state)
block.body.early_derived_secret_reveals = [early_derived_secret_reveal]
@ -202,20 +192,21 @@ def test_early_derived_secret_reveal(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_custody_slashing(spec, state):
# NOTE: this test is only for full crosslink (minimal config), not for mainnet
if not is_full_crosslink(spec, state):
# skip
return
state = transition_to_valid_shard_slot(spec, state)
transition_to_valid_shard_slot(spec, state)
# Build shard block
shard = 0
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
# Create slashable shard block body
validator_index = spec.get_beacon_committee(state, state.slot, committee_index)[0]
custody_secret = get_custody_secret(spec, state, validator_index)
custody_secret = spec.get_custody_secret(
state,
validator_index,
privkeys[validator_index],
spec.get_current_epoch(state),
)
slashable_body = get_custody_slashable_test_vector(spec, custody_secret, length=100, slashable=True)
shard_block = build_shard_block(spec, state, shard, body=slashable_body, slot=state.slot, signed=True)
shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]}

View File

@ -4,12 +4,12 @@ from eth2spec.test.context import (
expect_assertion_error,
spec_state_test,
with_all_phases_except,
only_full_crosslink,
)
from eth2spec.test.helpers.shard_block import (
build_shard_block,
sign_shard_block,
)
from eth2spec.test.helpers.shard_transitions import is_full_crosslink
from eth2spec.test.helpers.state import next_slot, transition_to_valid_shard_slot, transition_to
@ -46,15 +46,14 @@ def run_shard_blocks(spec, shard_state, signed_shard_block, beacon_parent_state,
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_valid_shard_block(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
signed_shard_block = build_shard_block(spec, state, shard, slot=beacon_state.slot, signed=True)
yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state)
@ -66,12 +65,11 @@ def test_valid_shard_block(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_invalid_shard_parent_root(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
beacon_state = transition_to_valid_shard_slot(spec, state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
@ -83,12 +81,10 @@ def test_invalid_shard_parent_root(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_invalid_beacon_parent_root(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
@ -100,12 +96,10 @@ def test_invalid_beacon_parent_root(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_invalid_slot(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
@ -118,12 +112,10 @@ def test_invalid_slot(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@only_full_crosslink
def test_invalid_proposer_index(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)
@ -141,13 +133,10 @@ def test_invalid_proposer_index(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_out_of_bound_offset(spec, state):
# TODO: Handle this edge case properly
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
slot = (
beacon_state.shard_states[shard].slot
@ -165,12 +154,10 @@ def test_out_of_bound_offset(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_invalid_offset(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
# 4 is not in `SHARD_BLOCK_OFFSETS`
shard = 0
slot = beacon_state.shard_states[shard].slot + 4
@ -186,12 +173,10 @@ def test_invalid_offset(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_empty_block_body(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, body=b'', signed=True)
@ -207,12 +192,10 @@ def test_empty_block_body(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_invalid_signature(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False)
@ -228,12 +211,10 @@ def test_invalid_signature(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_max_offset(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
slot = beacon_state.shard_states[shard].slot + spec.SHARD_BLOCK_OFFSETS[spec.MAX_SHARD_BLOCKS_PER_ATTESTATION - 1]
transition_to(spec, beacon_state, slot)
@ -247,13 +228,11 @@ def test_max_offset(spec, state):
@with_all_phases_except([PHASE0])
@spec_state_test
@always_bls
@only_full_crosslink
def test_pending_shard_parent_block(spec, state):
if not is_full_crosslink(spec, state):
# skip
return
# Block N
beacon_state = transition_to_valid_shard_slot(spec, state)
beacon_state = state.copy()
transition_to_valid_shard_slot(spec, beacon_state)
shard = 0
shard_state = beacon_state.shard_states[shard]
signed_shard_block_1 = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True)

View File

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

View File

@ -1,129 +0,0 @@
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.test.context import PHASE0, spec_state_test, with_all_phases_except, never_bls
from eth2spec.test.helpers.attestations import get_valid_on_time_attestation
from eth2spec.test.helpers.shard_block import (
build_shard_block,
get_shard_transitions,
get_committee_index_of_shard,
)
from eth2spec.test.helpers.fork_choice import add_block_to_store, get_anchor_root
from eth2spec.test.helpers.state import state_transition_and_sign_block
from eth2spec.test.helpers.block import build_empty_block
def run_on_shard_block(spec, store, shard_store, signed_block, valid=True):
if not valid:
try:
spec.on_shard_block(store, shard_store, signed_block)
except AssertionError:
return
else:
assert False
spec.on_shard_block(store, shard_store, signed_block)
assert shard_store.signed_blocks[hash_tree_root(signed_block.message)] == signed_block
def apply_shard_block(spec, store, shard_store, beacon_parent_state, shard_blocks_buffer):
shard = shard_store.shard
body = b'\x56' * 4
shard_head_root = spec.get_shard_head(store, shard_store)
shard_parent_state = shard_store.block_states[shard_head_root]
assert shard_parent_state.slot != beacon_parent_state.slot
shard_block = build_shard_block(
spec, beacon_parent_state, shard,
shard_parent_state=shard_parent_state, slot=beacon_parent_state.slot, body=body, signed=True
)
shard_blocks_buffer.append(shard_block)
run_on_shard_block(spec, store, shard_store, shard_block)
assert spec.get_shard_head(store, shard_store) == shard_block.message.hash_tree_root()
def check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer):
pending_shard_blocks = spec.get_pending_shard_blocks(store, shard_store)
assert pending_shard_blocks == shard_blocks_buffer
def is_in_offset_sets(spec, beacon_head_state, shard):
offset_slots = spec.compute_offset_slots(
beacon_head_state.shard_states[shard].slot, beacon_head_state.slot + 1
)
return beacon_head_state.slot in offset_slots
def apply_shard_and_beacon(spec, state, store, shard_store, shard_blocks_buffer):
store.time = store.time + spec.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH
shard = shard_store.shard
committee_index = get_committee_index_of_shard(spec, state, state.slot, shard)
has_shard_committee = committee_index is not None # has committee of `shard` at this slot
beacon_block = build_empty_block(spec, state, slot=state.slot + 1)
# If next slot has committee of `shard`, add `shard_transtion` to the proposing beacon block
if has_shard_committee and len(shard_blocks_buffer) > 0:
# Sanity check `get_pending_shard_blocks` function
check_pending_shard_blocks(spec, store, shard_store, shard_blocks_buffer)
# Use temporary next state to get ShardTransition of shard block
shard_transitions = get_shard_transitions(
spec,
state,
shard_block_dict={shard: shard_blocks_buffer},
)
shard_transition = shard_transitions[shard]
attestation = get_valid_on_time_attestation(
spec,
state,
index=committee_index,
shard_transition=shard_transition,
signed=False,
)
assert attestation.data.shard == shard
beacon_block.body.attestations = [attestation]
beacon_block.body.shard_transitions = shard_transitions
# Clear buffer
shard_blocks_buffer.clear()
signed_beacon_block = state_transition_and_sign_block(spec, state, beacon_block) # transition!
add_block_to_store(spec, store, signed_beacon_block)
assert spec.get_head(store) == beacon_block.hash_tree_root()
# On shard block at transitioned `state.slot`
if is_in_offset_sets(spec, state, shard):
# The created shard block would be appended to `shard_blocks_buffer`
apply_shard_block(spec, store, shard_store, state, shard_blocks_buffer)
return has_shard_committee
@with_all_phases_except([PHASE0])
@spec_state_test
@never_bls # Set to never_bls for testing `check_pending_shard_blocks`
def test_basic(spec, state):
spec.PHASE_1_GENESIS_SLOT = 0 # NOTE: mock genesis slot here
state = spec.upgrade_to_phase1(state)
shard = spec.Shard(1)
# Initialization
store = spec.get_forkchoice_store(state)
anchor_root = get_anchor_root(spec, state)
assert spec.get_head(store) == anchor_root
shard_store = spec.get_forkchoice_shard_store(state, shard)
shard_head_root = spec.get_shard_head(store, shard_store)
assert shard_head_root == state.shard_states[shard].latest_block_root
assert shard_store.block_states[shard_head_root].slot == 1
assert shard_store.block_states[shard_head_root] == state.shard_states[shard]
# For mainnet config, it's possible that only one committee of `shard` per epoch.
# we set this counter to test more rounds.
shard_committee_counter = 2
shard_blocks_buffer = []
while shard_committee_counter > 0:
has_shard_committee = apply_shard_and_beacon(
spec, state, store, shard_store, shard_blocks_buffer
)
if has_shard_committee:
shard_committee_counter -= 1

View File

@ -74,3 +74,16 @@ def test_get_start_shard_previous_slot(spec, state):
- spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
) % active_shard_count
assert start_shard == expected_start_shard
@with_all_phases_except([PHASE0])
@spec_state_test
def test_get_start_shard_far_past_epoch(spec, state):
initial_epoch = spec.get_current_epoch(state)
initial_start_slot = spec.compute_start_slot_at_epoch(initial_epoch)
initial_start_shard = state.current_epoch_start_shard
for _ in range(spec.MAX_SHARDS + 2):
next_epoch(spec, state)
assert spec.get_start_shard(state, initial_start_slot) == initial_start_shard

View File

@ -165,6 +165,9 @@ bls_setting: int -- optional, can have 3 different values:
but there is no change of outcome when running the test if BLS is ON or OFF.
1: known as "BLS required" - if the test validity is strictly dependent on BLS being ON
2: known as "BLS ignored" - if the test validity is strictly dependent on BLS being OFF
reveal_deadlines_setting: -- optional, can have 2 different values:
0: default, `process_reveal_deadlines` is ON.
1: `process_reveal_deadlines` is OFF.
```

View File

@ -7,9 +7,10 @@ Sanity tests to cover a series of one or more blocks being processed, aiming to
### `meta.yaml`
```yaml
description: string -- Optional. Description of test case, purely for debugging purposes.
bls_setting: int -- see general test-format spec.
blocks_count: int -- the number of blocks processed in this test.
description: string -- Optional. Description of test case, purely for debugging purposes.
bls_setting: int -- see general test-format spec.
reveal_deadlines_setting: int -- see general test-format spec.
blocks_count: int -- the number of blocks processed in this test.
```

View File

@ -1,24 +1,17 @@
from typing import Iterable
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.phase0.epoch_processing import (
test_process_final_updates,
test_process_justification_and_finalization,
test_process_registry_updates,
test_process_rewards_and_penalties,
test_process_slashings
)
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from importlib import reload
from importlib import reload, import_module
from eth2spec.config import config_util
from eth2spec.test.context import PHASE0
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
def create_provider(fork_name: str, handler_name: str,
tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
@ -27,27 +20,40 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
tests_src = import_module(tests_src_mod_name)
return generate_from_tests(
runner_name='epoch_processing',
handler_name=handler_name,
src=tests_src,
fork_name=PHASE0,
fork_name=fork_name,
)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
if __name__ == "__main__":
gen_runner.run_generator("epoch_processing", [
create_provider('final_updates', test_process_final_updates, 'minimal'),
create_provider('final_updates', test_process_final_updates, 'mainnet'),
create_provider('justification_and_finalization', test_process_justification_and_finalization, 'minimal'),
create_provider('justification_and_finalization', test_process_justification_and_finalization, 'mainnet'),
create_provider('registry_updates', test_process_registry_updates, 'minimal'),
create_provider('registry_updates', test_process_registry_updates, 'mainnet'),
create_provider('rewards_and_penalties', test_process_rewards_and_penalties, 'minimal'),
# runs full epochs filled with data, with uncached ssz. Disabled for now.
# create_provider('rewards_and_penalties', test_process_rewards_and_penalties, 'mainnet'),
create_provider('slashings', test_process_slashings, 'minimal'),
create_provider('slashings', test_process_slashings, 'mainnet'),
phase_0_mods = {key: 'eth2spec.test.phase0.epoch_processing.test_process_' + key for key in [
'final_updates',
'justification_and_finalization',
'registry_updates',
'rewards_and_penalties',
'slashings',
]}
phase_1_mods = {**{key: 'eth2spec.test.phase1.epoch_processing.test_process_' + key for key in [
'challenge_deadlines',
'custody_final_updates',
'reveal_deadlines',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
gen_runner.run_generator(f"epoch_processing", [
create_provider(PHASE0, key, mod_name, 'minimal') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"epoch_processing", [
create_provider(PHASE0, key, mod_name, 'mainnet') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"epoch_processing", [
create_provider(PHASE1, key, mod_name, 'minimal') for key, mod_name in phase_1_mods.items()
])
gen_runner.run_generator(f"epoch_processing", [
create_provider(PHASE1, key, mod_name, 'mainnet') for key, mod_name in phase_1_mods.items()
])

View File

@ -4,7 +4,7 @@ from importlib import reload
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from eth2spec.test.context import PHASE0
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.test.phase0.finality import test_finality
from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
@ -12,7 +12,7 @@ from eth2spec.phase1 import spec as spec_phase1
from eth2spec.utils import bls
def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
def create_provider(fork_name: str, handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
@ -26,14 +26,18 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin
runner_name='finality',
handler_name=handler_name,
src=tests_src,
fork_name=PHASE0,
fork_name=fork_name,
)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
if __name__ == "__main__":
# No additional phase 1 specific rewards tests, yet.
key = 'finality'
gen_runner.run_generator("finality", [
create_provider('finality', test_finality, 'minimal'),
create_provider('finality', test_finality, 'mainnet'),
create_provider(PHASE0, 'finality', test_finality, 'minimal'),
create_provider(PHASE0, 'finality', test_finality, 'mainnet'),
create_provider(PHASE1, 'finality', test_finality, 'minimal'),
create_provider(PHASE1, 'finality', test_finality, 'mainnet'),
])

View File

@ -1,26 +1,17 @@
from typing import Iterable
from eth2spec.test.phase0.block_processing import (
test_process_attestation,
test_process_attester_slashing,
test_process_block_header,
test_process_deposit,
test_process_proposer_slashing,
test_process_voluntary_exit,
)
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from importlib import reload
from importlib import reload, import_module
from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
def create_provider(fork_name: str, handler_name: str,
tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
@ -29,28 +20,44 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
tests_src = import_module(tests_src_mod_name)
return generate_from_tests(
runner_name='operations',
handler_name=handler_name,
src=tests_src,
fork_name=PHASE0,
fork_name=fork_name,
)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
if __name__ == "__main__":
gen_runner.run_generator("operations", [
create_provider('attestation', test_process_attestation, 'minimal'),
create_provider('attestation', test_process_attestation, 'mainnet'),
create_provider('attester_slashing', test_process_attester_slashing, 'minimal'),
create_provider('attester_slashing', test_process_attester_slashing, 'mainnet'),
create_provider('block_header', test_process_block_header, 'minimal'),
create_provider('block_header', test_process_block_header, 'mainnet'),
create_provider('deposit', test_process_deposit, 'minimal'),
create_provider('deposit', test_process_deposit, 'mainnet'),
create_provider('proposer_slashing', test_process_proposer_slashing, 'minimal'),
create_provider('proposer_slashing', test_process_proposer_slashing, 'mainnet'),
create_provider('voluntary_exit', test_process_voluntary_exit, 'minimal'),
create_provider('voluntary_exit', test_process_voluntary_exit, 'mainnet'),
phase_0_mods = {key: 'eth2spec.test.phase0.block_processing.test_process_' + key for key in [
'attestation',
'attester_slashing',
'block_header',
'deposit',
'proposer_slashing',
'voluntary_exit',
]}
phase_1_mods = {**{key: 'eth2spec.test.phase1.block_processing.test_process_' + key for key in [
'attestation',
'chunk_challenge',
'custody_key_reveal',
'custody_slashing',
'early_derived_secret_reveal',
'shard_transition',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
gen_runner.run_generator(f"operations", [
create_provider(PHASE0, key, mod_name, 'minimal') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"operations", [
create_provider(PHASE0, key, mod_name, 'mainnet') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"operations", [
create_provider(PHASE1, key, mod_name, 'minimal') for key, mod_name in phase_1_mods.items()
])
gen_runner.run_generator(f"operations", [
create_provider(PHASE1, key, mod_name, 'mainnet') for key, mod_name in phase_1_mods.items()
])

View File

@ -1,22 +1,17 @@
from typing import Iterable
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.phase0.rewards import (
test_basic,
test_leak,
test_random,
)
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from importlib import reload
from importlib import reload, import_module
from eth2spec.config import config_util
from eth2spec.test.context import PHASE0
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(tests_src, config_name: str) -> gen_typing.TestProvider:
def create_provider(fork_name: str, handler_name: str,
tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
@ -25,22 +20,35 @@ def create_provider(tests_src, config_name: str) -> gen_typing.TestProvider:
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
tests_src = import_module(tests_src_mod_name)
return generate_from_tests(
runner_name='rewards',
handler_name='core',
handler_name=handler_name,
src=tests_src,
fork_name=PHASE0,
fork_name=fork_name,
)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
if __name__ == "__main__":
gen_runner.run_generator("rewards", [
create_provider(test_basic, 'minimal'),
create_provider(test_basic, 'mainnet'),
create_provider(test_leak, 'minimal'),
create_provider(test_leak, 'mainnet'),
create_provider(test_random, 'minimal'),
create_provider(test_random, 'mainnet'),
phase_0_mods = {key: 'eth2spec.test.phase0.rewards.test_' + key for key in [
'basic',
'leak',
'random',
]}
# No additional phase 1 specific rewards tests, yet.
phase_1_mods = phase_0_mods
gen_runner.run_generator(f"rewards", [
create_provider(PHASE0, key, mod_name, 'minimal') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"rewards", [
create_provider(PHASE0, key, mod_name, 'mainnet') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"rewards", [
create_provider(PHASE1, key, mod_name, 'minimal') for key, mod_name in phase_1_mods.items()
])
gen_runner.run_generator(f"rewards", [
create_provider(PHASE1, key, mod_name, 'mainnet') for key, mod_name in phase_1_mods.items()
])

View File

@ -1,19 +1,17 @@
from typing import Iterable
from importlib import reload
from gen_base import gen_runner, gen_typing
from gen_from_tests.gen import generate_from_tests
from eth2spec.test.context import PHASE0
from eth2spec.test.phase0.sanity import test_blocks, test_slots
from importlib import reload, import_module
from eth2spec.config import config_util
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils import bls
def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typing.TestProvider:
def create_provider(fork_name: str, handler_name: str,
tests_src_mod_name: str, config_name: str) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
config_util.prepare_config(configs_path, config_name)
reload(spec_phase0)
@ -22,20 +20,36 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
tests_src = import_module(tests_src_mod_name)
return generate_from_tests(
runner_name='sanity',
handler_name=handler_name,
src=tests_src,
fork_name=PHASE0,
fork_name=fork_name,
)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
if __name__ == "__main__":
gen_runner.run_generator("sanity", [
create_provider('blocks', test_blocks, 'minimal'),
create_provider('blocks', test_blocks, 'mainnet'),
create_provider('slots', test_slots, 'minimal'),
create_provider('slots', test_slots, 'mainnet'),
phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [
'blocks',
'slots',
]}
phase_1_mods = {**{key: 'eth2spec.test.phase1.sanity.test_' + key for key in [
'blocks', # more phase 1 specific block tests
'shard_blocks',
]}, **phase_0_mods} # also run the previous phase 0 tests (but against phase 1 spec)
gen_runner.run_generator(f"sanity", [
create_provider(PHASE0, key, mod_name, 'minimal') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"sanity", [
create_provider(PHASE0, key, mod_name, 'mainnet') for key, mod_name in phase_0_mods.items()
])
gen_runner.run_generator(f"sanity", [
create_provider(PHASE1, key, mod_name, 'minimal') for key, mod_name in phase_1_mods.items()
])
gen_runner.run_generator(f"sanity", [
create_provider(PHASE1, key, mod_name, 'mainnet') for key, mod_name in phase_1_mods.items()
])

View File

@ -12,4 +12,3 @@ def invalid_cases():
yield "byte_rev_nibble", invalid_test_case(lambda: b'\x10')
yield "byte_0x80", invalid_test_case(lambda: b'\x80')
yield "byte_full", invalid_test_case(lambda: b'\xff')

View File

@ -85,7 +85,7 @@ def valid_cases():
def mod_offset(b: bytes, offset_index: int, change: Callable[[int], int]):
return b[:offset_index] + \
(change(int.from_bytes(b[offset_index:offset_index + 4], byteorder='little')) & 0xffffffff) \
.to_bytes(length=4, byteorder='little') + \
.to_bytes(length=4, byteorder='little') + \
b[offset_index + 4:]

View File

@ -7,8 +7,9 @@ from gen_base import gen_runner, gen_typing
from eth2spec.debug import random_value, encode
from eth2spec.config import config_util
from eth2spec.phase0 import spec
from eth2spec.test.context import PHASE0
from eth2spec.phase0 import spec as spec_phase0
from eth2spec.phase1 import spec as spec_phase1
from eth2spec.test.context import PHASE0, PHASE1
from eth2spec.utils.ssz.ssz_typing import Container
from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root,
@ -16,11 +17,12 @@ from eth2spec.utils.ssz.ssz_impl import (
)
MAX_BYTES_LENGTH = 100
MAX_BYTES_LENGTH = 1000
MAX_LIST_LENGTH = 10
def create_test_case(rng: Random, typ, mode: random_value.RandomizationMode, chaos: bool) -> Iterable[gen_typing.TestCasePart]:
def create_test_case(rng: Random, typ,
mode: random_value.RandomizationMode, chaos: bool) -> Iterable[gen_typing.TestCasePart]:
value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
yield "value", "data", encode.encode(value)
yield "serialized", "ssz", serialize(value)
@ -30,14 +32,15 @@ def create_test_case(rng: Random, typ, mode: random_value.RandomizationMode, cha
yield "roots", "data", roots_data
def get_spec_ssz_types():
def get_spec_ssz_types(spec):
return [
(name, value) for (name, value) in getmembers(spec, isclass)
if issubclass(value, Container) and value != Container # only the subclasses, not the imported base class
]
def ssz_static_cases(seed: int, name, ssz_type, mode: random_value.RandomizationMode, chaos: bool, count: int):
def ssz_static_cases(fork_name: str, seed: int, name, ssz_type,
mode: random_value.RandomizationMode, chaos: bool, count: int):
random_mode_name = mode.to_name()
# Reproducible RNG
@ -45,7 +48,7 @@ def ssz_static_cases(seed: int, name, ssz_type, mode: random_value.Randomization
for i in range(count):
yield gen_typing.TestCase(
fork_name=PHASE0,
fork_name=fork_name,
runner_name='ssz_static',
handler_name=name,
suite_name=f"ssz_{random_mode_name}{'_chaos' if chaos else ''}",
@ -54,19 +57,23 @@ def ssz_static_cases(seed: int, name, ssz_type, mode: random_value.Randomization
)
def create_provider(config_name: str, seed: int, mode: random_value.RandomizationMode, chaos: bool,
def create_provider(fork_name, config_name: str, seed: int, mode: random_value.RandomizationMode, chaos: bool,
cases_if_random: int) -> gen_typing.TestProvider:
def prepare_fn(configs_path: str) -> str:
# Apply changes to presets, this affects some of the vector types.
config_util.prepare_config(configs_path, config_name)
reload(spec)
reload(spec_phase0)
reload(spec_phase1)
return config_name
def cases_fn() -> Iterable[gen_typing.TestCase]:
count = cases_if_random if chaos or mode.is_changing() else 1
spec = spec_phase0
if fork_name == PHASE1:
spec = spec_phase1
for (i, (name, ssz_type)) in enumerate(get_spec_ssz_types()):
yield from ssz_static_cases(seed * 1000 + i, name, ssz_type, mode, chaos, count)
for (i, (name, ssz_type)) in enumerate(get_spec_ssz_types(spec)):
yield from ssz_static_cases(fork_name, seed * 1000 + i, name, ssz_type, mode, chaos, count)
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
@ -80,10 +87,11 @@ if __name__ == "__main__":
seed += 1
settings.append((seed, "minimal", random_value.RandomizationMode.mode_random, True, 30))
seed += 1
settings.append((seed, "mainnet", random_value.RandomizationMode.mode_random, False, 5))
seed += 1
# settings.append((seed, "mainnet", random_value.RandomizationMode.mode_random, False, 5))
# seed += 1
gen_runner.run_generator("ssz_static", [
create_provider(config_name, seed, mode, chaos, cases_if_random)
for (seed, config_name, mode, chaos, cases_if_random) in settings
])
for fork in [PHASE0, PHASE1]:
gen_runner.run_generator("ssz_static", [
create_provider(fork, config_name, seed, mode, chaos, cases_if_random)
for (seed, config_name, mode, chaos, cases_if_random) in settings
])