diff --git a/.circleci/config.yml b/.circleci/config.yml index 565b1c92f5..2767bec704 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: + - run: + name: Run linter for test generators + command: make lint_generators + build_deposit_contract: 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 + - 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: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - restore_deposit_contract_compiler_cached_venv + key: nix-store-test-v2 + - attach_workspace: + at: /tmp/ - run: - name: Install deposit contract compiler requirements - command: make install_deposit_contract_compiler - - save_deposit_contract_compiler_cached_venv - install_deposit_contract_tester: + 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 diff --git a/.gitattributes b/.gitattributes index c2b17bf1a4..52031de51c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -*.vy linguist-language=Python +*.sol linguist-language=Solidity diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..6fcad6b97a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/ds-test"] + path = solidity_deposit_contract/lib/ds-test + url = https://github.com/dapphub/ds-test diff --git a/Makefile b/Makefile index 5ef25289e8..8fa1044445 100644 --- a/Makefile +++ b/Makefile @@ -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 +lint_generators: pyspec + . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ + flake8 --config $(LINTER_CONFIG_FILE) -test_deposit_contract: - cd $(DEPOSIT_CONTRACT_TESTER_DIR); . venv/bin/activate; \ - python -m pytest . +compile_deposit_contract: + @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) -install_deposit_contract_compiler: - cd $(DEPOSIT_CONTRACT_COMPILER_DIR); python3.7 -m venv venv; . venv/bin/activate; pip3.7 install -r requirements.txt +test_deposit_contract: + dapp test -v --fuzz-runs 5 -compile_deposit_contract: - cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \ - python3.7 deposit_contract/compile.py ../contracts/validator_registration.vy +install_deposit_contract_web3_tester: + cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt -test_compile_deposit_contract: - cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \ - python3.7 -m pytest . +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 diff --git a/README.md b/README.md index 78ce5f31f8..a3ca7c5864 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/configs/README.md b/configs/README.md index be3c60e6f2..353cd35dbc 100644 --- a/configs/README.md +++ b/configs/README.md @@ -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. diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index 616baaf345..fbb12ba2ee 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -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 diff --git a/configs/mainnet/phase1.yaml b/configs/mainnet/phase1.yaml index 8c08009576..1d689f2b56 100644 --- a/configs/mainnet/phase1.yaml +++ b/configs/mainnet/phase1.yaml @@ -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 diff --git a/configs/minimal/phase0.yaml b/configs/minimal/phase0.yaml index 78cd601770..0ee81738ef 100644 --- a/configs/minimal/phase0.yaml +++ b/configs/minimal/phase0.yaml @@ -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 diff --git a/configs/minimal/phase1.yaml b/configs/minimal/phase1.yaml index 7fbc7e5a30..cf17ba52a7 100644 --- a/configs/minimal/phase1.yaml +++ b/configs/minimal/phase1.yaml @@ -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 diff --git a/deposit_contract/README.md b/deposit_contract/README.md deleted file mode 100644 index e7ec591e57..0000000000 --- a/deposit_contract/README.md +++ /dev/null @@ -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 -``` diff --git a/deposit_contract/compiler/deposit_contract/compile.py b/deposit_contract/compiler/deposit_contract/compile.py deleted file mode 100644 index 6d67818786..0000000000 --- a/deposit_contract/compiler/deposit_contract/compile.py +++ /dev/null @@ -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) diff --git a/deposit_contract/compiler/deposit_contract/test_compile.py b/deposit_contract/compiler/deposit_contract/test_compile.py deleted file mode 100644 index 6922cf80c9..0000000000 --- a/deposit_contract/compiler/deposit_contract/test_compile.py +++ /dev/null @@ -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"] diff --git a/deposit_contract/compiler/requirements.txt b/deposit_contract/compiler/requirements.txt deleted file mode 100644 index 209d430124..0000000000 --- a/deposit_contract/compiler/requirements.txt +++ /dev/null @@ -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 diff --git a/deposit_contract/compiler/setup.py b/deposit_contract/compiler/setup.py deleted file mode 100644 index add6d80436..0000000000 --- a/deposit_contract/compiler/setup.py +++ /dev/null @@ -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 -) diff --git a/deposit_contract/contracts/validator_registration.json b/deposit_contract/contracts/validator_registration.json deleted file mode 100644 index 12cfd74ac0..0000000000 --- a/deposit_contract/contracts/validator_registration.json +++ /dev/null @@ -1 +0,0 @@ -{"abi": [{"name": "DepositEvent", "inputs": [{"type": "bytes", "name": "pubkey", "indexed": false}, {"type": "bytes", "name": "withdrawal_credentials", "indexed": false}, {"type": "bytes", "name": "amount", "indexed": false}, {"type": "bytes", "name": "signature", "indexed": false}, {"type": "bytes", "name": "index", "indexed": false}], "anonymous": false, "type": "event"}, {"outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor"}, {"name": "get_deposit_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 95628}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 18231}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "pubkey"}, {"type": "bytes", "name": "withdrawal_credentials"}, {"type": "bytes", "name": "signature"}, {"type": "bytes32", "name": "deposit_data_root"}], "constant": false, "payable": true, "type": "function", "gas": 1342274}], "bytecode": "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6101406000601f818352015b600061014051602081106100b757600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e357600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012157600080fd5b60c0519050606051600161014051018060405190131561014057600080fd5b809190121561014e57600080fd5b6020811061015b57600080fd5b600260c052602060c02001555b81516001018083528114156100a4575b505061123556600436101561000d576110b0565b600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05260001561027f575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100e8578060000360020a82046100ef565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561011a57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610163578060000360020a820461016a565b8060020a82025b905090506101a0525b81516001018083528114156100cb575b5050601860086020820661020001602082840111156101a157600080fd5b60208061022082610180600060045af15050818152809050905090508051602001806102c08284600060045af16101d757600080fd5b50506102c05160206001820306601f82010390506103206102c0516020818352015b826103205110151561020a57610226565b6000610320516102e001535b81516001018083528114156101f9575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b60006102805111151561025b57610277565b602061028051036102a001516020610280510361028052610249565b610160515650005b63c5f2892f600051141561050e57341561029857600080fd5b6000610140526101405161016052600154610180526101a060006020818352015b600160016101805116141561033a5760006101a051602081106102db57600080fd5b600060c052602060c02001546020826102400101526020810190506101605160208261024001015260208101905080610240526102409050602060c0825160208401600060025af161032c57600080fd5b60c0519050610160526103a8565b6000610160516020826101c00101526020810190506101a0516020811061036057600080fd5b600260c052602060c02001546020826101c0010152602081019050806101c0526101c09050602060c0825160208401600060025af161039e57600080fd5b60c0519050610160525b61018060026103b657600080fd5b60028151048152505b81516001018083528114156102b9575b505060006101605160208261046001015260208101905061014051610160516101805163806732896102e0526001546103005261030051600658016100a9565b506103605260006103c0525b6103605160206001820306601f82010390506103c05110151561043d57610456565b6103c05161038001526103c0516020016103c05261041b565b61018052610160526101405261036060088060208461046001018260208501600060045af150508051820191505060006018602082066103e001602082840111156104a057600080fd5b60208061040082610140600060045af150508181528090509050905060188060208461046001018260208501600060045af150508051820191505080610460526104609050602060c0825160208401600060025af16104fe57600080fd5b60c051905060005260206000f350005b63621fd130600051141561061c57341561052757600080fd5b6380673289610140526001546101605261016051600658016100a9565b506101c0526000610220525b6101c05160206001820306601f8201039050610220511015156105725761058b565b610220516101e001526102205160200161022052610550565b6101c08051602001806102808284600060045af16105a857600080fd5b50506102805160206001820306601f82010390506102e0610280516020818352015b826102e0511015156105db576105f7565b60006102e0516102a001535b81516001018083528114156105ca575b5050506020610260526040610280510160206001820306601f8201039050610260f350005b632289511860005114156110af57605060043560040161014037603060043560040135111561064a57600080fd5b60406024356004016101c037602060243560040135111561066a57600080fd5b608060443560040161022037606060443560040135111561068a57600080fd5b63ffffffff6001541061069c57600080fd5b633b9aca006102e0526102e0516106b257600080fd5b6102e05134046102c052633b9aca006102c05110156106d057600080fd5b603061014051146106e057600080fd5b60206101c051146106f057600080fd5b6060610220511461070057600080fd5b610140610360525b6103605151602061036051016103605261036061036051101561072a57610708565b6380673289610380526102c0516103a0526103a051600658016100a9565b50610400526000610460525b6104005160206001820306601f8201039050610460511015156107765761078f565b6104605161042001526104605160200161046052610754565b610340610360525b61036051526020610360510361036052610140610360511015156107ba57610797565b6104008051602001806103008284600060045af16107d757600080fd5b5050610140610480525b61048051516020610480510161048052610480610480511015610803576107e1565b63806732896104a0526001546104c0526104c051600658016100a9565b50610520526000610580525b6105205160206001820306601f82010390506105805110151561084e57610867565b610580516105400152610580516020016105805261082c565b610460610480525b61048051526020610480510361048052610140610480511015156108925761086f565b6105208051602001806105a08284600060045af16108af57600080fd5b505060a061062052610620516106605261014080516020018061062051610660018284600060045af16108e157600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516040818352015b826106005110151561091e5761093f565b600061060051610620516106800101535b815160010180835281141561090d575b505050602061062051610660015160206001820306601f82010390506106205101016106205261062051610680526101c080516020018061062051610660018284600060045af161098f57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b82610600511015156109cc576109ed565b600061060051610620516106800101535b81516001018083528114156109bb575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106a05261030080516020018061062051610660018284600060045af1610a3d57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b8261060051101515610a7a57610a9b565b600061060051610620516106800101535b8151600101808352811415610a69575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106c05261022080516020018061062051610660018284600060045af1610aeb57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516060818352015b8261060051101515610b2857610b49565b600061060051610620516106800101535b8151600101808352811415610b17575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106e0526105a080516020018061062051610660018284600060045af1610b9957600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b8261060051101515610bd657610bf7565b600061060051610620516106800101535b8151600101808352811415610bc5575b505050602061062051610660015160206001820306601f8201039050610620510101610620527f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c561062051610660a160006107005260006101406030806020846107c001018260208501600060045af150508051820191505060006010602082066107400160208284011115610c8c57600080fd5b60208061076082610700600060045af15050818152809050905090506010806020846107c001018260208501600060045af1505080518201915050806107c0526107c09050602060c0825160208401600060025af1610cea57600080fd5b60c0519050610720526000600060406020820661086001610220518284011115610d1357600080fd5b6060806108808260206020880688030161022001600060045af1505081815280905090509050602060c0825160208401600060025af1610d5257600080fd5b60c0519050602082610a600101526020810190506000604060206020820661092001610220518284011115610d8657600080fd5b6060806109408260206020880688030161022001600060045af15050818152809050905090506020806020846109e001018260208501600060045af1505080518201915050610700516020826109e0010152602081019050806109e0526109e09050602060c0825160208401600060025af1610e0157600080fd5b60c0519050602082610a6001015260208101905080610a6052610a609050602060c0825160208401600060025af1610e3857600080fd5b60c0519050610840526000600061072051602082610b000101526020810190506101c0602080602084610b0001018260208501600060045af150508051820191505080610b0052610b009050602060c0825160208401600060025af1610e9d57600080fd5b60c0519050602082610c800101526020810190506000610300600880602084610c0001018260208501600060045af15050805182019150506000601860208206610b800160208284011115610ef157600080fd5b602080610ba082610700600060045af1505081815280905090509050601880602084610c0001018260208501600060045af150508051820191505061084051602082610c0001015260208101905080610c0052610c009050602060c0825160208401600060025af1610f6257600080fd5b60c0519050602082610c8001015260208101905080610c8052610c809050602060c0825160208401600060025af1610f9957600080fd5b60c0519050610ae052606435610ae05114610fb357600080fd5b6001805460018254011015610fc757600080fd5b6001815401815550600154610d0052610d2060006020818352015b60016001610d005116141561101757610ae051610d20516020811061100657600080fd5b600060c052602060c02001556110ab565b6000610d20516020811061102a57600080fd5b600060c052602060c0200154602082610d40010152602081019050610ae051602082610d4001015260208101905080610d4052610d409050602060c0825160208401600060025af161107b57600080fd5b60c0519050610ae052610d00600261109257600080fd5b60028151048152505b8151600101808352811415610fe2575b5050005b5b60006000fd5b61017f6112350361017f60003961017f611235036000f3"} \ No newline at end of file diff --git a/deposit_contract/contracts/validator_registration.vy b/deposit_contract/contracts/validator_registration.vy deleted file mode 100644 index 671252e2e4..0000000000 --- a/deposit_contract/contracts/validator_registration.vy +++ /dev/null @@ -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 - diff --git a/deposit_contract/tester/deposit_contract/__init__.py b/deposit_contract/tester/deposit_contract/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/setup.py b/setup.py index d1f7b35cea..b024e8d7f5 100644 --- a/setup.py +++ b/setup.py @@ -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 @@ def get_spec(file_name: str) -> SpecObject: 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 @@ def get_spec(file_name: str) -> SpecObject: 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 @@ def wrapper(*args, **kw): # type: ignore 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 @@ def combine_constants(old_constants: Dict[str, str], new_constants: Dict[str, st '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 @@ def finalize_options(self): 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 diff --git a/solidity_deposit_contract/AUTHORS b/solidity_deposit_contract/AUTHORS new file mode 100644 index 0000000000..4d06c4c575 --- /dev/null +++ b/solidity_deposit_contract/AUTHORS @@ -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.) diff --git a/solidity_deposit_contract/README.md b/solidity_deposit_contract/README.md new file mode 100644 index 0000000000..0388d7d2f5 --- /dev/null +++ b/solidity_deposit_contract/README.md @@ -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 +``` diff --git a/solidity_deposit_contract/deposit_contract.json b/solidity_deposit_contract/deposit_contract.json new file mode 100644 index 0000000000..060fe729c5 --- /dev/null +++ b/solidity_deposit_contract/deposit_contract.json @@ -0,0 +1 @@ +{"abi": [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"pubkey","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"withdrawal_credentials","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"amount","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"signature","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"index","type":"bytes"}],"name":"DepositEvent","type":"event"},{"inputs":[{"internalType":"bytes","name":"pubkey","type":"bytes"},{"internalType":"bytes","name":"withdrawal_credentials","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes32","name":"deposit_data_root","type":"bytes32"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"get_deposit_count","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"get_deposit_root","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}], "bytecode": "0x608060405234801561001057600080fd5b5060005b601f8110156101025760026021826020811061002c57fe5b01546021836020811061003b57fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b602083106100925780518252601f199092019160209182019101610073565b51815160209384036101000a60001901801990921691161790526040519190930194509192505080830381855afa1580156100d1573d6000803e3d6000fd5b5050506040513d60208110156100e657600080fd5b5051602160018301602081106100f857fe5b0155600101610014565b506118d680620001136000396000f3fe60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033"} \ No newline at end of file diff --git a/solidity_deposit_contract/deposit_contract.sol b/solidity_deposit_contract/deposit_contract.sol new file mode 100644 index 0000000000..21d0b895ae --- /dev/null +++ b/solidity_deposit_contract/deposit_contract.sol @@ -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]; + } +} diff --git a/solidity_deposit_contract/lib/ds-test b/solidity_deposit_contract/lib/ds-test new file mode 160000 index 0000000000..eb7148d43c --- /dev/null +++ b/solidity_deposit_contract/lib/ds-test @@ -0,0 +1 @@ +Subproject commit eb7148d43c1ca6f9890361e2e2378364af2430ba diff --git a/solidity_deposit_contract/shell.nix b/solidity_deposit_contract/shell.nix new file mode 100644 index 0000000000..d0727d49a8 --- /dev/null +++ b/solidity_deposit_contract/shell.nix @@ -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 ]; +} diff --git a/solidity_deposit_contract/tests/deposit_contract.t.sol b/solidity_deposit_contract/tests/deposit_contract.t.sol new file mode 100644 index 0000000000..c4a6718ba0 --- /dev/null +++ b/solidity_deposit_contract/tests/deposit_contract.t.sol @@ -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)); + } +} diff --git a/solidity_deposit_contract/tests/vyper_setup.sol b/solidity_deposit_contract/tests/vyper_setup.sol new file mode 100644 index 0000000000..15a5b6e59d --- /dev/null +++ b/solidity_deposit_contract/tests/vyper_setup.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.6.0; + +contract VyperSetup { + // Bytecode from https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/deposit_contract/contracts/validator_registration.vy + bytes constant public depositCode = hex"740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6101406000601f818352015b600061014051602081106100b757600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e357600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012157600080fd5b60c0519050606051600161014051018060405190131561014057600080fd5b809190121561014e57600080fd5b6020811061015b57600080fd5b600260c052602060c02001555b81516001018083528114156100a4575b505061123556600436101561000d576110b0565b600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05260001561027f575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100e8578060000360020a82046100ef565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561011a57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610163578060000360020a820461016a565b8060020a82025b905090506101a0525b81516001018083528114156100cb575b5050601860086020820661020001602082840111156101a157600080fd5b60208061022082610180600060045af15050818152809050905090508051602001806102c08284600060045af16101d757600080fd5b50506102c05160206001820306601f82010390506103206102c0516020818352015b826103205110151561020a57610226565b6000610320516102e001535b81516001018083528114156101f9575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b60006102805111151561025b57610277565b602061028051036102a001516020610280510361028052610249565b610160515650005b63c5f2892f600051141561050e57341561029857600080fd5b6000610140526101405161016052600154610180526101a060006020818352015b600160016101805116141561033a5760006101a051602081106102db57600080fd5b600060c052602060c02001546020826102400101526020810190506101605160208261024001015260208101905080610240526102409050602060c0825160208401600060025af161032c57600080fd5b60c0519050610160526103a8565b6000610160516020826101c00101526020810190506101a0516020811061036057600080fd5b600260c052602060c02001546020826101c0010152602081019050806101c0526101c09050602060c0825160208401600060025af161039e57600080fd5b60c0519050610160525b61018060026103b657600080fd5b60028151048152505b81516001018083528114156102b9575b505060006101605160208261046001015260208101905061014051610160516101805163806732896102e0526001546103005261030051600658016100a9565b506103605260006103c0525b6103605160206001820306601f82010390506103c05110151561043d57610456565b6103c05161038001526103c0516020016103c05261041b565b61018052610160526101405261036060088060208461046001018260208501600060045af150508051820191505060006018602082066103e001602082840111156104a057600080fd5b60208061040082610140600060045af150508181528090509050905060188060208461046001018260208501600060045af150508051820191505080610460526104609050602060c0825160208401600060025af16104fe57600080fd5b60c051905060005260206000f350005b63621fd130600051141561061c57341561052757600080fd5b6380673289610140526001546101605261016051600658016100a9565b506101c0526000610220525b6101c05160206001820306601f8201039050610220511015156105725761058b565b610220516101e001526102205160200161022052610550565b6101c08051602001806102808284600060045af16105a857600080fd5b50506102805160206001820306601f82010390506102e0610280516020818352015b826102e0511015156105db576105f7565b60006102e0516102a001535b81516001018083528114156105ca575b5050506020610260526040610280510160206001820306601f8201039050610260f350005b632289511860005114156110af57605060043560040161014037603060043560040135111561064a57600080fd5b60406024356004016101c037602060243560040135111561066a57600080fd5b608060443560040161022037606060443560040135111561068a57600080fd5b63ffffffff6001541061069c57600080fd5b633b9aca006102e0526102e0516106b257600080fd5b6102e05134046102c052633b9aca006102c05110156106d057600080fd5b603061014051146106e057600080fd5b60206101c051146106f057600080fd5b6060610220511461070057600080fd5b610140610360525b6103605151602061036051016103605261036061036051101561072a57610708565b6380673289610380526102c0516103a0526103a051600658016100a9565b50610400526000610460525b6104005160206001820306601f8201039050610460511015156107765761078f565b6104605161042001526104605160200161046052610754565b610340610360525b61036051526020610360510361036052610140610360511015156107ba57610797565b6104008051602001806103008284600060045af16107d757600080fd5b5050610140610480525b61048051516020610480510161048052610480610480511015610803576107e1565b63806732896104a0526001546104c0526104c051600658016100a9565b50610520526000610580525b6105205160206001820306601f82010390506105805110151561084e57610867565b610580516105400152610580516020016105805261082c565b610460610480525b61048051526020610480510361048052610140610480511015156108925761086f565b6105208051602001806105a08284600060045af16108af57600080fd5b505060a061062052610620516106605261014080516020018061062051610660018284600060045af16108e157600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516040818352015b826106005110151561091e5761093f565b600061060051610620516106800101535b815160010180835281141561090d575b505050602061062051610660015160206001820306601f82010390506106205101016106205261062051610680526101c080516020018061062051610660018284600060045af161098f57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b82610600511015156109cc576109ed565b600061060051610620516106800101535b81516001018083528114156109bb575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106a05261030080516020018061062051610660018284600060045af1610a3d57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b8261060051101515610a7a57610a9b565b600061060051610620516106800101535b8151600101808352811415610a69575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106c05261022080516020018061062051610660018284600060045af1610aeb57600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516060818352015b8261060051101515610b2857610b49565b600061060051610620516106800101535b8151600101808352811415610b17575b505050602061062051610660015160206001820306601f820103905061062051010161062052610620516106e0526105a080516020018061062051610660018284600060045af1610b9957600080fd5b505061062051610660015160206001820306601f82010390506106006106205161066001516020818352015b8261060051101515610bd657610bf7565b600061060051610620516106800101535b8151600101808352811415610bc5575b505050602061062051610660015160206001820306601f8201039050610620510101610620527f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c561062051610660a160006107005260006101406030806020846107c001018260208501600060045af150508051820191505060006010602082066107400160208284011115610c8c57600080fd5b60208061076082610700600060045af15050818152809050905090506010806020846107c001018260208501600060045af1505080518201915050806107c0526107c09050602060c0825160208401600060025af1610cea57600080fd5b60c0519050610720526000600060406020820661086001610220518284011115610d1357600080fd5b6060806108808260206020880688030161022001600060045af1505081815280905090509050602060c0825160208401600060025af1610d5257600080fd5b60c0519050602082610a600101526020810190506000604060206020820661092001610220518284011115610d8657600080fd5b6060806109408260206020880688030161022001600060045af15050818152809050905090506020806020846109e001018260208501600060045af1505080518201915050610700516020826109e0010152602081019050806109e0526109e09050602060c0825160208401600060025af1610e0157600080fd5b60c0519050602082610a6001015260208101905080610a6052610a609050602060c0825160208401600060025af1610e3857600080fd5b60c0519050610840526000600061072051602082610b000101526020810190506101c0602080602084610b0001018260208501600060045af150508051820191505080610b0052610b009050602060c0825160208401600060025af1610e9d57600080fd5b60c0519050602082610c800101526020810190506000610300600880602084610c0001018260208501600060045af15050805182019150506000601860208206610b800160208284011115610ef157600080fd5b602080610ba082610700600060045af1505081815280905090509050601880602084610c0001018260208501600060045af150508051820191505061084051602082610c0001015260208101905080610c0052610c009050602060c0825160208401600060025af1610f6257600080fd5b60c0519050602082610c8001015260208101905080610c8052610c809050602060c0825160208401600060025af1610f9957600080fd5b60c0519050610ae052606435610ae05114610fb357600080fd5b6001805460018254011015610fc757600080fd5b6001815401815550600154610d0052610d2060006020818352015b60016001610d005116141561101757610ae051610d20516020811061100657600080fd5b600060c052602060c02001556110ab565b6000610d20516020811061102a57600080fd5b600060c052602060c0200154602082610d40010152602081019050610ae051602082610d4001015260208101905080610d4052610d409050602060c0825160208401600060025af161107b57600080fd5b60c0519050610ae052610d00600261109257600080fd5b60028151048152505b8151600101808352811415610fe2575b5050005b5b60006000fd5b61017f6112350361017f60003961017f611235036000f3"; + function write(bytes memory _code) public returns (address target) { + assembly { + target := create(0, add(_code, 0x20), mload(_code)) + } + } + + function deployDeposit() public returns (address) { + return write(depositCode); + } +} + diff --git a/deposit_contract/tester/requirements.txt b/solidity_deposit_contract/web3_tester/requirements.txt similarity index 100% rename from deposit_contract/tester/requirements.txt rename to solidity_deposit_contract/web3_tester/requirements.txt diff --git a/deposit_contract/tester/setup.py b/solidity_deposit_contract/web3_tester/setup.py similarity index 100% rename from deposit_contract/tester/setup.py rename to solidity_deposit_contract/web3_tester/setup.py diff --git a/deposit_contract/compiler/deposit_contract/__init__.py b/solidity_deposit_contract/web3_tester/tests/__init__.py similarity index 100% rename from deposit_contract/compiler/deposit_contract/__init__.py rename to solidity_deposit_contract/web3_tester/tests/__init__.py diff --git a/deposit_contract/tester/deposit_contract/conftest.py b/solidity_deposit_contract/web3_tester/tests/conftest.py similarity index 95% rename from deposit_contract/tester/deposit_contract/conftest.py rename to solidity_deposit_contract/web3_tester/tests/conftest.py index c20501b112..57b5f6b7a7 100644 --- a/deposit_contract/tester/deposit_contract/conftest.py +++ b/solidity_deposit_contract/web3_tester/tests/conftest.py @@ -15,7 +15,7 @@ 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) diff --git a/deposit_contract/tester/deposit_contract/test_deposit.py b/solidity_deposit_contract/web3_tester/tests/test_deposit.py similarity index 99% rename from deposit_contract/tester/deposit_contract/test_deposit.py rename to solidity_deposit_contract/web3_tester/tests/test_deposit.py index 5fa98e2323..4b16446a16 100644 --- a/deposit_contract/tester/deposit_contract/test_deposit.py +++ b/solidity_deposit_contract/web3_tester/tests/test_deposit.py @@ -6,7 +6,7 @@ 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, ) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 15c8924a4e..69bc48d69f 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -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 ( diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index b4f8d30364..89c5bb22bf 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -16,7 +16,7 @@ - [Deposit amount](#deposit-amount) - [Withdrawal credentials](#withdrawal-credentials) - [`DepositEvent` log](#depositevent-log) -- [Vyper code](#vyper-code) +- [Solidity code](#solidity-code) @@ -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. diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 8e1f651216..018cbec4bc 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -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)}, ) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index aa8d4aace2..e90a91341c 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -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. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index e379df03c2..95411f1b6d 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -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) diff --git a/specs/phase0/weak-subjectivity.md b/specs/phase0/weak-subjectivity.md new file mode 100644 index 0000000000..07c3083a3c --- /dev/null +++ b/specs/phase0/weak-subjectivity.md @@ -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 + + + + + + +- [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) + + + + +## 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. diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 9fa04336c1..fd6b63d9c2 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -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) ``` diff --git a/specs/phase1/custody-game.md b/specs/phase1/custody-game.md index 3688442a0c..82eb35d869 100644 --- a/specs/phase1/custody-game.md +++ b/specs/phase1/custody-game.md @@ -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 diff --git a/specs/phase1/fork-choice.md b/specs/phase1/fork-choice.md index 41787cfd09..3845b2b747 100644 --- a/specs/phase1/fork-choice.md +++ b/specs/phase1/fork-choice.md @@ -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) @@ -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 ``` diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index e83e9ef4a3..a094b01fc8 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -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) diff --git a/specs/phase1/shard-fork-choice.md b/specs/phase1/shard-fork-choice.md index 8416009d73..380269d13e 100644 --- a/specs/phase1/shard-fork-choice.md +++ b/specs/phase1/shard-fork-choice.md @@ -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 diff --git a/specs/phase1/validator.md b/specs/phase1/validator.md index ad2fa09e3c..2bfea017e1 100644 --- a/specs/phase1/validator.md +++ b/specs/phase1/validator.md @@ -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), diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 4f0c5c9faf..607dd0946e 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -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 diff --git a/tests/core/gen_helpers/gen_base/gen_runner.py b/tests/core/gen_helpers/gen_base/gen_runner.py index 32f3594b35..a22073c00a 100644 --- a/tests/core/gen_helpers/gen_base/gen_runner.py +++ b/tests/core/gen_helpers/gen_base/gen_runner.py @@ -2,6 +2,7 @@ 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 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 output_part(out_kind: str, name: str, fn: Callable[[Path, ], None]): 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 output_part(out_kind: str, name: str, fn: Callable[[Path, ], None]): 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}") diff --git a/tests/core/gen_helpers/requirements.txt b/tests/core/gen_helpers/requirements.txt index dc3f619042..e7cdd30ea2 100644 --- a/tests/core/gen_helpers/requirements.txt +++ b/tests/core/gen_helpers/requirements.txt @@ -1,2 +1,3 @@ ruamel.yaml==0.16.5 eth-utils==1.6.0 +pytest>=4.4 diff --git a/tests/core/gen_helpers/setup.py b/tests/core/gen_helpers/setup.py index b674dbfb6b..e9fc1a7877 100644 --- a/tests/core/gen_helpers/setup.py +++ b/tests/core/gen_helpers/setup.py @@ -5,6 +5,7 @@ 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", ] ) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index e96a87111c..d61567cd13 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -0.12.2 \ No newline at end of file +0.12.3 \ No newline at end of file diff --git a/tests/core/pyspec/eth2spec/config/config_util.py b/tests/core/pyspec/eth2spec/config/config_util.py index 1977e5b054..3f6764e98d 100644 --- a/tests/core/pyspec/eth2spec/config/config_util.py +++ b/tests/core/pyspec/eth2spec/config/config_util.py @@ -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 diff --git a/tests/core/pyspec/eth2spec/debug/random_value.py b/tests/core/pyspec/eth2spec/debug/random_value.py index 9fc3be9788..33eb25c0c6 100644 --- a/tests/core/pyspec/eth2spec/debug/random_value.py +++ b/tests/core/pyspec/eth2spec/debug/random_value.py @@ -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: diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index 21f7c7abbd..b961ee0828 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -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( diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 0d953cf241..cfd6724ed7 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -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 entry(*args, **kw): 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 entry(*args, **kw): 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 wrapper(*args, **kw): 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 wrapper(*args, **kw): 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 diff --git a/tests/core/pyspec/eth2spec/test/exceptions.py b/tests/core/pyspec/eth2spec/test/exceptions.py new file mode 100644 index 0000000000..c553ec3744 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/exceptions.py @@ -0,0 +1,2 @@ +class SkippedTest(Exception): + ... diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 3445f9ff64..b924da378b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -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, diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py index 4cb9947fa7..b3a8c0a957 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ b/tests/core/pyspec/eth2spec/test/helpers/custody.py @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 04e36ea849..85437e98a9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -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) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index 6e508e3970..d10d1ee7bc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -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) diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index c70a47742f..f53d8f7116 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -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): diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 192e0390d1..82d4903117 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -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) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py index e2a6a3fe03..b9becc1fd0 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py @@ -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.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 + 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) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 72bdd3e92a..0137ddca42 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -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 = [] diff --git a/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py index d2d3d44050..c414f645e0 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py +++ b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index c183865a52..721d68add1 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -21,8 +21,14 @@ 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 create_signed_exit(index): # 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 diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py index 859fc797f0..b470ab0792 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_get_head.py @@ -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 @@ @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 diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py index ffd8b417d5..63b0572b1f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py @@ -2,6 +2,7 @@ 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) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py index 016326b30c..7fa32b86c6 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py @@ -2,10 +2,11 @@ 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) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py index 93f3bd9bb5..9cf0e9e26d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py @@ -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( diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py index 4ef1d667a2..e916010b25 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_chunk_challenge.py @@ -6,12 +6,15 @@ 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 diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py index 07cd769961..fc7efa5bc9 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_custody_slashing.py @@ -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, diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index 8668316863..b0a51557af 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -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.shard_transitions import ( run_shard_transitions_processing, - is_full_crosslink, ) from eth2spec.test.helpers.shard_block import ( build_shard_block, @@ -22,7 +22,7 @@ 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 diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py index 675b0d8da6..e270ff615e 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py @@ -5,11 +5,13 @@ 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)) diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py index 93fea19cf2..0541411da0 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py @@ -10,7 +10,7 @@ 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)) diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py index da1a604469..5777e184aa 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py @@ -4,7 +4,9 @@ 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) diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index f69cd47935..922b604ad4 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -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 @@ 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]} diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py index 041b882abf..ab66314e5b 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -4,12 +4,12 @@ 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) diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py new file mode 100644 index 0000000000..30eaa8d80b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_block.py @@ -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() diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py deleted file mode 100644 index a87a859171..0000000000 --- a/tests/core/pyspec/eth2spec/test/phase1/unittests/fork_choice/test_on_shard_head.py +++ /dev/null @@ -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 diff --git a/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py index c443545b9b..a802d6c3cb 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py +++ b/tests/core/pyspec/eth2spec/test/phase1/unittests/test_get_start_shard.py @@ -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 diff --git a/tests/formats/README.md b/tests/formats/README.md index 63b9a53903..36a5ec21b2 100644 --- a/tests/formats/README.md +++ b/tests/formats/README.md @@ -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. ``` diff --git a/tests/formats/sanity/blocks.md b/tests/formats/sanity/blocks.md index 991bc35d22..44b37ed5ee 100644 --- a/tests/formats/sanity/blocks.md +++ b/tests/formats/sanity/blocks.md @@ -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. ``` diff --git a/tests/generators/epoch_processing/main.py b/tests/generators/epoch_processing/main.py index 418d6c7505..fe8e0ee921 100644 --- a/tests/generators/epoch_processing/main.py +++ b/tests/generators/epoch_processing/main.py @@ -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 prepare_fn(configs_path: str) -> str: 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() ]) diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py index dca0ecb8dc..ef2d8293f4 100644 --- a/tests/generators/finality/main.py +++ b/tests/generators/finality/main.py @@ -4,7 +4,7 @@ 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.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 cases_fn() -> Iterable[gen_typing.TestCase]: 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'), ]) diff --git a/tests/generators/operations/main.py b/tests/generators/operations/main.py index be490c5b2e..1acf45e477 100644 --- a/tests/generators/operations/main.py +++ b/tests/generators/operations/main.py @@ -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 prepare_fn(configs_path: str) -> str: 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() ]) diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py index dd82ee165a..23d0633b08 100644 --- a/tests/generators/rewards/main.py +++ b/tests/generators/rewards/main.py @@ -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 prepare_fn(configs_path: str) -> str: 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() ]) diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 2feaaf09dd..83166f0cf0 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -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 prepare_fn(configs_path: str) -> str: 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() ]) diff --git a/tests/generators/ssz_generic/ssz_boolean.py b/tests/generators/ssz_generic/ssz_boolean.py index 9ff36ba88d..ec22c01be9 100644 --- a/tests/generators/ssz_generic/ssz_boolean.py +++ b/tests/generators/ssz_generic/ssz_boolean.py @@ -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') - diff --git a/tests/generators/ssz_generic/ssz_container.py b/tests/generators/ssz_generic/ssz_container.py index cf7c338398..9cd155f76b 100644 --- a/tests/generators/ssz_generic/ssz_container.py +++ b/tests/generators/ssz_generic/ssz_container.py @@ -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:] diff --git a/tests/generators/ssz_static/main.py b/tests/generators/ssz_static/main.py index b9cb51db08..a30908fe9e 100644 --- a/tests/generators/ssz_static/main.py +++ b/tests/generators/ssz_static/main.py @@ -7,8 +7,9 @@ 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 @@ ) -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 @@ def cases_fn() -> Iterable[gen_typing.TestCase]: 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 - - 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 - ]) + # settings.append((seed, "mainnet", random_value.RandomizationMode.mode_random, False, 5)) + # seed += 1 + + 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 + ])