diff --git a/.circleci/config.yml b/.circleci/config.yml index 9a7172866..41e809207 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,15 +60,15 @@ jobs: - restore_cache: key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} - restore_cached_venv: - venv_name: v1-pyspec - reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}' + venv_name: v1-test_libs + reqs_checksum: '{{ checksum "test_libs/pyspec/setup.py" }}' - run: name: Install pyspec requirements command: make install_test - save_cached_venv: - venv_name: v1-pyspec - reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}' - venv_path: ./test_libs/pyspec/venv + venv_name: v1-test_libs + reqs_checksum: '{{ checksum "test_libs/pyspec/setup.py" }}' + venv_path: ./test_libs/venv test: docker: - image: circleci/python:3.6 @@ -77,8 +77,8 @@ jobs: - restore_cache: key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} - restore_cached_venv: - venv_name: v1-pyspec - reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}' + venv_name: v1-test_libs + reqs_checksum: '{{ checksum "test_libs/pyspec/setup.py" }}' - run: name: Run py-tests command: make citest diff --git a/.gitignore b/.gitignore index ce047240a..84938d298 100644 --- a/.gitignore +++ b/.gitignore @@ -4,12 +4,19 @@ venv .venvs .venv /.pytest_cache +*.egg +*.egg-info +eggs +.eggs build/ output/ -yaml_tests/ +eth2.0-spec-tests/ .pytest_cache # Dynamically built from Markdown spec test_libs/pyspec/eth2spec/phase0/spec.py + +# vscode +.vscode/** diff --git a/Makefile b/Makefile index 71d150983..135124898 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ SPEC_DIR = ./specs SCRIPT_DIR = ./scripts TEST_LIBS_DIR = ./test_libs PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec -YAML_TEST_DIR = ./yaml_tests +CONFIG_HELPERS_DIR = $(TEST_LIBS_DIR)/config_helpers +YAML_TEST_DIR = ./eth2.0-spec-tests/tests GENERATOR_DIR = ./test_generators CONFIGS_DIR = ./configs @@ -23,21 +24,26 @@ all: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS) clean: rm -rf $(YAML_TEST_DIR) rm -rf $(GENERATOR_VENVS) - rm -rf $(PY_SPEC_DIR)/venv $(PY_SPEC_DIR)/.pytest_cache + rm -rf $(TEST_LIBS_DIR)/venv + rm -rf $(PY_SPEC_DIR)/.pytest_cache rm -rf $(PY_SPEC_ALL_TARGETS) # "make gen_yaml_tests" to run generators -gen_yaml_tests: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS) +gen_yaml_tests: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_TARGETS) # installs the packages to run pyspec tests install_test: - cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt; + cd $(TEST_LIBS_DIR); python3 -m venv venv; . venv/bin/activate; \ + cd ..; cd $(CONFIG_HELPERS_DIR); pip3 install -e .; \ + cd ../..; cd $(PY_SPEC_DIR); pip3 install -e .[dev]; test: $(PY_SPEC_ALL_TARGETS) - cd $(PY_SPEC_DIR); . venv/bin/activate; python -m pytest -m minimal_config . + cd $(TEST_LIBS_DIR); . venv/bin/activate; \ + cd ..; cd $(PY_SPEC_DIR); python -m pytest .; citest: $(PY_SPEC_ALL_TARGETS) - cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; . venv/bin/activate; python -m pytest --junitxml=test-reports/eth2spec/test_results.xml -m minimal_config . + cd $(TEST_LIBS_DIR); . venv/bin/activate; \ + cd ..; cd $(PY_SPEC_DIR); mkdir -p test-reports/eth2spec; python -m pytest --junitxml=test-reports/eth2spec/test_results.xml . # "make pyspec" to create the pyspec for all phases. pyspec: $(PY_SPEC_ALL_TARGETS) @@ -54,24 +60,30 @@ CURRENT_DIR = ${CURDIR} # The function that builds a set of suite files, by calling a generator for the given type (param 1) define build_yaml_tests - $(info running generator $(1)) - # Create the output - mkdir -p $(YAML_TEST_DIR)$(1) - - # 1) Create a virtual environment - # 2) Activate the venv, this is where dependencies are installed for the generator - # 3) Install all the necessary requirements - # 4) Run the generator. The generator is assumed to have an "main.py" file. - # 5) We output to the tests dir (generator program should accept a "-o " argument. - cd $(GENERATOR_DIR)$(1); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt; python3 main.py -o $(CURRENT_DIR)/$(YAML_TEST_DIR)$(1) -c $(CURRENT_DIR)/$(CONFIGS_DIR) - - $(info generator $(1) finished) + # Started! + # Create output directory + # Navigate to the generator + # Create a virtual environment, if it does not exist already + # Activate the venv, this is where dependencies are installed for the generator + # Install all the necessary requirements + # Run the generator. The generator is assumed to have an "main.py" file. + # We output to the tests dir (generator program should accept a "-o " argument. + echo "generator $(1) started"; \ + mkdir -p $(YAML_TEST_DIR)$(1); \ + cd $(GENERATOR_DIR)$(1); \ + if ! test -d venv; then python3 -m venv venv; fi; \ + . venv/bin/activate; \ + pip3 install -r requirements.txt; \ + python3 main.py -o $(CURRENT_DIR)/$(YAML_TEST_DIR)$(1) -c $(CURRENT_DIR)/$(CONFIGS_DIR); \ + echo "generator $(1) finished" endef # The tests dir itself is simply build by creating the directory (recursively creating deeper directories if necessary) $(YAML_TEST_DIR): $(info creating directory, to output yaml targets to: ${YAML_TEST_TARGETS}) mkdir -p $@ +$(YAML_TEST_DIR)/: + $(info ignoring duplicate yaml tests dir) # For any target within the tests dir, build it using the build_yaml_tests function. # (creation of output dir is a dependency) diff --git a/README.md b/README.md index aa5b7e302..b2b369e11 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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 eth2.0/Serenity, see the [sharding FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). +To learn more about sharding and eth2.0/Serenity, see the [sharding FAQ](https://github.com/ethereum/wiki/wiki/Sharding-FAQ) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). This repo hosts the current eth2.0 specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed upon changes to spec can be made through pull requests. @@ -11,10 +11,10 @@ This repo hosts the current eth2.0 specifications. Discussions about design rati Core specifications for eth2.0 client validation can be found in [specs/core](specs/core). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are: * [Phase 0 -- The Beacon Chain](specs/core/0_beacon-chain.md) -* [Phase 1 -- Custody game](specs/core/1_custody-game.md) +* [Phase 1 -- Custody Game](specs/core/1_custody-game.md) * [Phase 1 -- Shard Data Chains](specs/core/1_shard-data-chains.md) -Accompanying documents can be found in [specs](specs) and include +Accompanying documents can be found in [specs](specs) and include: * [SimpleSerialize (SSZ) spec](specs/simple-serialize.md) * [BLS signature verification](specs/bls_signature.md) * [General test format](specs/test_formats/README.md) diff --git a/configs/constant_presets/mainnet.yaml b/configs/constant_presets/mainnet.yaml index 8b9dade73..d63c617b7 100644 --- a/configs/constant_presets/mainnet.yaml +++ b/configs/constant_presets/mainnet.yaml @@ -10,7 +10,7 @@ SHARD_COUNT: 1024 # 2**7 (= 128) TARGET_COMMITTEE_SIZE: 128 # 2**12 (= 4,096) -MAX_ATTESTATION_PARTICIPANTS: 4096 +MAX_INDICES_PER_ATTESTATION: 4096 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 # 2**16 (= 65,536) @@ -32,7 +32,7 @@ DEPOSIT_CONTRACT_TREE_DEPTH: 32 # 2**0 * 10**9 (= 1,000,000,000) Gwei MIN_DEPOSIT_AMOUNT: 1000000000 # 2**5 * 10**9 (= 32,000,000,000) Gwei -MAX_DEPOSIT_AMOUNT: 32000000000 +MAX_EFFECTIVE_BALANCE: 32000000000 # 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000 # 2**0 * 10**9 (= 1,000,000,000) Gwei @@ -44,7 +44,6 @@ HIGH_BALANCE_INCREMENT: 1000000000 GENESIS_FORK_VERSION: 0x00000000 # 0, GENESIS_EPOCH is derived from this constant GENESIS_SLOT: 0 -GENESIS_START_SHARD: 0 # 2**64 - 1 FAR_FUTURE_EPOCH: 18446744073709551615 BLS_WITHDRAWAL_PREFIX_BYTE: 0x00 @@ -110,8 +109,8 @@ MAX_ATTESTATIONS: 128 MAX_DEPOSITS: 16 # 2**4 (= 16) MAX_VOLUNTARY_EXITS: 16 -# 2**4 (= 16) -MAX_TRANSFERS: 16 +# Originally 2**4 (= 16), disabled for now. +MAX_TRANSFERS: 0 # Signature domains diff --git a/configs/constant_presets/minimal.yaml b/configs/constant_presets/minimal.yaml index edc447c45..711f6737d 100644 --- a/configs/constant_presets/minimal.yaml +++ b/configs/constant_presets/minimal.yaml @@ -10,7 +10,7 @@ SHARD_COUNT: 8 # [customized] unsecure, but fast TARGET_COMMITTEE_SIZE: 4 # 2**12 (= 4,096) -MAX_ATTESTATION_PARTICIPANTS: 4096 +MAX_INDICES_PER_ATTESTATION: 4096 # 2**2 (= 4) MIN_PER_EPOCH_CHURN_LIMIT: 4 # 2**16 (= 65,536) @@ -32,7 +32,7 @@ DEPOSIT_CONTRACT_TREE_DEPTH: 32 # 2**0 * 10**9 (= 1,000,000,000) Gwei MIN_DEPOSIT_AMOUNT: 1000000000 # 2**5 * 10**9 (= 32,000,000,000) Gwei -MAX_DEPOSIT_AMOUNT: 32000000000 +MAX_EFFECTIVE_BALANCE: 32000000000 # 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000 # 2**0 * 10**9 (= 1,000,000,000) Gwei @@ -44,7 +44,6 @@ HIGH_BALANCE_INCREMENT: 1000000000 GENESIS_FORK_VERSION: 0x00000000 # 0, GENESIS_EPOCH is derived from this constant GENESIS_SLOT: 0 -GENESIS_START_SHARD: 0 # 2**64 - 1 FAR_FUTURE_EPOCH: 18446744073709551615 BLS_WITHDRAWAL_PREFIX_BYTE: 0x00 @@ -110,8 +109,8 @@ MAX_ATTESTATIONS: 128 MAX_DEPOSITS: 16 # 2**4 (= 16) MAX_VOLUNTARY_EXITS: 16 -# 2**4 (= 16) -MAX_TRANSFERS: 16 +# Originally 2**4 (= 16), disabled for now. +MAX_TRANSFERS: 0 # Signature domains diff --git a/specs/bls_signature.md b/specs/bls_signature.md index beef19df5..18e2d8c9a 100644 --- a/specs/bls_signature.md +++ b/specs/bls_signature.md @@ -86,7 +86,7 @@ def hash_to_G2(message_hash: Bytes32, domain: uint64) -> [uint384]: ### `modular_squareroot` -`modular_squareroot(x)` returns a solution `y` to `y**2 % q == x`, and `None` if none exists. If there are two solutions the one with higher imaginary component is favored; if both solutions have equal imaginary component the one with higher real component is favored (note that this is equivalent to saying that the single solution with either imaginary component > p/2 or imaginary component zero and real component > p/2 is favored). +`modular_squareroot(x)` returns a solution `y` to `y**2 % q == x`, and `None` if none exists. If there are two solutions, the one with higher imaginary component is favored; if both solutions have equal imaginary component, the one with higher real component is favored (note that this is equivalent to saying that the single solution with either imaginary component > p/2 or imaginary component zero and real component > p/2 is favored). The following is a sample implementation; implementers are free to implement modular square roots as they wish. Note that `x2 = -x1` is an _additive modular inverse_ so real and imaginary coefficients remain in `[0 .. q-1]`. `coerce_to_int(element: Fq) -> int` is a function that takes Fq element `element` (i.e. integers `mod q`) and converts it to a regular integer. diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index b6a89f679..55791e25f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1,6 +1,6 @@ # Ethereum 2.0 Phase 0 -- The Beacon Chain -**NOTICE**: This document is a work in progress for researchers and implementers. It reflects recent spec changes and takes precedence over the Python proof-of-concept implementation [[python-poc]](#ref-python-poc). +**NOTICE**: This document is a work in progress for researchers and implementers. ## Table of contents @@ -58,8 +58,6 @@ - [`is_active_validator`](#is_active_validator) - [`is_slashable_validator`](#is_slashable_validator) - [`get_active_validator_indices`](#get_active_validator_indices) - - [`get_balance`](#get_balance) - - [`set_balance`](#set_balance) - [`increase_balance`](#increase_balance) - [`decrease_balance`](#decrease_balance) - [`get_permuted_index`](#get_permuted_index) @@ -68,6 +66,7 @@ - [`get_shard_delta`](#get_shard_delta) - [`compute_committee`](#compute_committee) - [`get_crosslink_committees_at_slot`](#get_crosslink_committees_at_slot) + - [`get_block_root_at_slot`](#get_block_root_at_slot) - [`get_block_root`](#get_block_root) - [`get_state_root`](#get_state_root) - [`get_randao_mix`](#get_randao_mix) @@ -78,7 +77,6 @@ - [`get_attesting_indices`](#get_attesting_indices) - [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-) - [`bytes_to_int`](#bytes_to_int) - - [`get_effective_balance`](#get_effective_balance) - [`get_total_balance`](#get_total_balance) - [`get_domain`](#get_domain) - [`get_bitfield_bit`](#get_bitfield_bit) @@ -94,7 +92,6 @@ - [`bls_verify_multiple`](#bls_verify_multiple) - [`bls_aggregate_pubkeys`](#bls_aggregate_pubkeys) - [Routines for updating validator status](#routines-for-updating-validator-status) - - [`activate_validator`](#activate_validator) - [`initiate_validator_exit`](#initiate_validator_exit) - [`slash_validator`](#slash_validator) - [On genesis](#on-genesis) @@ -168,6 +165,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MAX_INDICES_PER_ATTESTATION` | `2**12` (= 4,096) | | `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) | | `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) | +| `BASE_REWARDS_PER_EPOCH` | `5` | | `SHUFFLE_ROUND_COUNT` | 90 | * For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](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.) @@ -183,9 +181,9 @@ These configurations are updated for releases, but may be out of sync during `de | Name | Value | Unit | | - | - | :-: | | `MIN_DEPOSIT_AMOUNT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | -| `MAX_DEPOSIT_AMOUNT` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | +| `MAX_EFFECTIVE_BALANCE` | `2**5 * 10**9` (= 32,000,000,000) | Gwei | | `EJECTION_BALANCE` | `2**4 * 10**9` (= 16,000,000,000) | Gwei | -| `HIGH_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | +| `EFFECTIVE_BALANCE_INCREMENT` | `2**0 * 10**9` (= 1,000,000,000) | Gwei | ### Initial values @@ -210,6 +208,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | | `MAX_CROSSLINK_EPOCHS` | `2**6` (= 64) | epochs | ~7 hours | +| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes | * `MAX_CROSSLINK_EPOCHS` should be a small constant times `SHARD_COUNT // SLOTS_PER_EPOCH` @@ -228,10 +227,10 @@ These configurations are updated for releases, but may be out of sync during `de | `BASE_REWARD_QUOTIENT` | `2**5` (= 32) | | `WHISTLEBLOWING_REWARD_QUOTIENT` | `2**9` (= 512) | | `PROPOSER_REWARD_QUOTIENT` | `2**3` (= 8) | -| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) | -| `MIN_PENALTY_QUOTIENT` | `2**5` (= 32) | +| `INACTIVITY_PENALTY_QUOTIENT` | `2**25` (= 33,554,432) | +| `MIN_SLASHING_PENALTY_QUOTIENT` | `2**5` (= 32) | -* The `BASE_REWARD_QUOTIENT` parameter dictates the per-epoch reward. It corresponds to ~2.54% annual interest assuming 10 million participating ETH in every epoch. +* **The `BASE_REWARD_QUOTIENT` is NOT final. Once all other protocol details are finalized it will be adjusted, to target a theoretical maximum total issuance of `2**21` ETH per year if `2**27` ETH is validating (and therefore `2**20` per year if `2**25` ETH is validating, etc etc)** * The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12 epochs` (~18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating [validators](#dfn-validator) to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline [validators](#dfn-validator) after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)` so after `INVERSE_SQRT_E_DROP_TIME` epochs it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. ### Max operations per block @@ -243,7 +242,7 @@ These configurations are updated for releases, but may be out of sync during `de | `MAX_ATTESTATIONS` | `2**7` (= 128) | | `MAX_DEPOSITS` | `2**4` (= 16) | | `MAX_VOLUNTARY_EXITS` | `2**4` (= 16) | -| `MAX_TRANSFERS` | `2**4` (= 16) | +| `MAX_TRANSFERS` | `0` | ### Signature domains @@ -392,8 +391,8 @@ The types are defined topologically to aid in facilitating an executable version 'withdrawable_epoch': 'uint64', # Was the validator slashed 'slashed': 'bool', - # Rounded balance - 'high_balance': 'uint64' + # Effective balance + 'effective_balance': 'uint64', } ``` @@ -407,6 +406,8 @@ The types are defined topologically to aid in facilitating an executable version 'data': AttestationData, # Inclusion slot 'inclusion_slot': 'uint64', + # Proposer index + 'proposer_index': 'uint64', } ``` @@ -669,6 +670,7 @@ def get_epoch_start_slot(epoch: Epoch) -> Slot: ``` ### `is_active_validator` + ```python def is_active_validator(validator: Validator, epoch: Epoch) -> bool: """ @@ -678,15 +680,14 @@ def is_active_validator(validator: Validator, epoch: Epoch) -> bool: ``` ### `is_slashable_validator` + ```python def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: """ Check if ``validator`` is slashable. """ - return ( - validator.activation_epoch <= epoch < validator.withdrawable_epoch and - validator.slashed is False - ) + return validator.slashed is False and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + ``` ### `get_active_validator_indices` @@ -699,39 +700,14 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> List[Valid return [i for i, v in enumerate(state.validator_registry) if is_active_validator(v, epoch)] ``` -### `get_balance` - -```python -def get_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Return the balance for a validator with the given ``index``. - """ - return state.balances[index] -``` - -### `set_balance` - -```python -def set_balance(state: BeaconState, index: ValidatorIndex, balance: Gwei) -> None: - """ - Set the balance for a validator with the given ``index`` in both ``BeaconState`` - and validator's rounded balance ``high_balance``. - """ - validator = state.validator_registry[index] - HALF_INCREMENT = HIGH_BALANCE_INCREMENT // 2 - if validator.high_balance > balance or validator.high_balance + 3 * HALF_INCREMENT < balance: - validator.high_balance = balance - balance % HIGH_BALANCE_INCREMENT - state.balances[index] = balance -``` - ### `increase_balance` ```python def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: """ - Increase the balance for a validator with the given ``index`` by ``delta``. + Increase validator balance by ``delta``. """ - set_balance(state, index, get_balance(state, index) + delta) + state.balances[index] += delta ``` ### `decrease_balance` @@ -739,11 +715,9 @@ def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> ```python def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: """ - Decrease the balance for a validator with the given ``index`` by ``delta``. - Set to ``0`` when underflow. + Decrease validator balance by ``delta`` with underflow protection. """ - current_balance = get_balance(state, index) - set_balance(state, index, current_balance - delta if current_balance >= delta else 0) + state.balances[index] = 0 if delta > state.balances[index] else state.balances[index] - delta ``` ### `get_permuted_index` @@ -788,14 +762,14 @@ def get_split_offset(list_size: int, chunks: int, index: int) -> int: ```python def get_epoch_committee_count(state: BeaconState, epoch: Epoch) -> int: """ - Return the number of committees in one epoch. + Return the number of committees at ``epoch``. """ - active_validators = get_active_validator_indices(state, epoch) + active_validator_indices = get_active_validator_indices(state, epoch) return max( 1, min( SHARD_COUNT // SLOTS_PER_EPOCH, - len(active_validators) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, + len(active_validator_indices) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, ) ) * SLOTS_PER_EPOCH ``` @@ -804,6 +778,9 @@ def get_epoch_committee_count(state: BeaconState, epoch: Epoch) -> int: ```python def get_shard_delta(state: BeaconState, epoch: Epoch) -> int: + """ + Return the number of shards to increment ``state.latest_start_shard`` during ``epoch``. + """ return min(get_epoch_committee_count(state, epoch), SHARD_COUNT - SHARD_COUNT // SLOTS_PER_EPOCH) ``` @@ -826,7 +803,7 @@ def compute_committee(validator_indices: List[ValidatorIndex], ] ``` -**Note**: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. +Note: this definition and the next few definitions are highly inefficient as algorithms, as they re-calculate many sub-expressions. Production implementations are expected to appropriately use caching/memoization to avoid redoing work. ### `get_crosslink_committees_at_slot` @@ -868,11 +845,11 @@ def get_crosslink_committees_at_slot(state: BeaconState, ] ``` -### `get_block_root` +### `get_block_root_at_slot` ```python -def get_block_root(state: BeaconState, - slot: Slot) -> Bytes32: +def get_block_root_at_slot(state: BeaconState, + slot: Slot) -> Bytes32: """ Return the block root at a recent ``slot``. """ @@ -880,7 +857,16 @@ def get_block_root(state: BeaconState, return state.latest_block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] ``` -`get_block_root(_, s)` should always return `signing_root` of the block in the beacon chain at slot `s`, and `get_crosslink_committees_at_slot(_, s)` should not change unless the [validator](#dfn-validator) registry changes. +### `get_block_root` + +```python +def get_block_root(state: BeaconState, + epoch: Epoch) -> Bytes32: + """ + Return the block root at a recent ``epoch``. + """ + return get_block_root_at_slot(state, get_epoch_start_slot(epoch)) +``` ### `get_state_root` @@ -893,6 +879,7 @@ def get_state_root(state: BeaconState, assert slot < state.slot <= slot + SLOTS_PER_HISTORICAL_ROOT return state.latest_state_roots[slot % SLOTS_PER_HISTORICAL_ROOT] ``` + ### `get_randao_mix` ```python @@ -940,14 +927,15 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: Return the beacon proposer index at ``state.slot``. """ current_epoch = get_current_epoch(state) - first_committee, _ = get_crosslink_committees_at_slot(state, state.slot)[0] + MAX_RANDOM_BYTE = 2**8 - 1 i = 0 while True: - candidate = first_committee[(current_epoch + i) % len(first_committee)] + candidate_index = first_committee[(current_epoch + i) % len(first_committee)] random_byte = hash(generate_seed(state, current_epoch) + int_to_bytes8(i // 32))[i % 32] - if get_effective_balance(state, candidate) * 256 > MAX_DEPOSIT_AMOUNT * random_byte: - return candidate + effective_balance = state.validator_registry[candidate_index].effective_balance + if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: + return candidate_index i += 1 ``` @@ -994,24 +982,14 @@ def bytes_to_int(data: bytes) -> int: return int.from_bytes(data, 'little') ``` -### `get_effective_balance` - -```python -def get_effective_balance(state: BeaconState, index: ValidatorIndex) -> Gwei: - """ - Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. - """ - return min(get_balance(state, index), MAX_DEPOSIT_AMOUNT) -``` - ### `get_total_balance` ```python -def get_total_balance(state: BeaconState, validators: List[ValidatorIndex]) -> Gwei: +def get_total_balance(state: BeaconState, indices: List[ValidatorIndex]) -> Gwei: """ Return the combined effective balance of an array of ``validators``. """ - return sum([get_effective_balance(state, i) for i in validators]) + return sum([state.validator_registry[index].effective_balance for index in indices]) ``` ### `get_domain` @@ -1195,22 +1173,6 @@ def get_churn_limit(state: BeaconState) -> int: Note: All functions in this section mutate `state`. -#### `activate_validator` - -```python -def activate_validator(state: BeaconState, index: ValidatorIndex) -> None: - """ - Activate the validator of the given ``index``. - Note that this function mutates ``state``. - """ - validator = state.validator_registry[index] - if state.slot == GENESIS_SLOT: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - else: - validator.activation_epoch = get_delayed_activation_exit_epoch(get_current_epoch(state)) -``` - #### `initiate_validator_exit` ```python @@ -1244,11 +1206,12 @@ def slash_validator(state: BeaconState, slashed_index: ValidatorIndex, whistlebl Slash the validator with index ``slashed_index``. Note that this function mutates ``state``. """ + current_epoch = get_current_epoch(state) initiate_validator_exit(state, slashed_index) state.validator_registry[slashed_index].slashed = True - state.validator_registry[slashed_index].withdrawable_epoch = get_current_epoch(state) + LATEST_SLASHED_EXIT_LENGTH - slashed_balance = get_effective_balance(state, slashed_index) - state.latest_slashed_balances[get_current_epoch(state) % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance + state.validator_registry[slashed_index].withdrawable_epoch = current_epoch + LATEST_SLASHED_EXIT_LENGTH + slashed_balance = state.validator_registry[slashed_index].effective_balance + state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH] += slashed_balance proposer_index = get_beacon_proposer_index(state) if whistleblower_index is None: @@ -1287,9 +1250,10 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], process_deposit(state, deposit) # Process genesis activations - for index in range(len(state.validator_registry)): - if get_effective_balance(state, index) >= MAX_DEPOSIT_AMOUNT: - activate_validator(state, index) + for index, validator in enumerate(state.validator_registry): + if validator.effective_balance >= MAX_EFFECTIVE_BALANCE: + validator.activation_eligibility_epoch = GENESIS_EPOCH + validator.activation_epoch = GENESIS_EPOCH genesis_active_index_root = hash_tree_root(get_active_validator_indices(state, GENESIS_EPOCH)) for index in range(LATEST_ACTIVE_INDEX_ROOTS_LENGTH): @@ -1298,7 +1262,6 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit], return state ``` - ## Beacon chain state transition function We now define the state transition function. At a high level, the state transition is made up of four parts: @@ -1316,7 +1279,7 @@ Transition section notes: Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid. -_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. +Note: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. ### State caching @@ -1346,13 +1309,30 @@ The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SL We define epoch transition helper functions: ```python -def get_current_total_balance(state: BeaconState) -> Gwei: +def get_total_active_balance(state: BeaconState) -> Gwei: return get_total_balance(state, get_active_validator_indices(state, get_current_epoch(state))) ``` ```python -def get_previous_total_balance(state: BeaconState) -> Gwei: - return get_total_balance(state, get_active_validator_indices(state, get_previous_epoch(state))) +def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: + assert epoch in (get_current_epoch(state), get_previous_epoch(state)) + return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations +``` + +```python +def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: + return [ + a for a in get_matching_source_attestations(state, epoch) + if a.data.target_root == get_block_root(state, epoch) + ] +``` + +```python +def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> List[PendingAttestation]: + return [ + a for a in get_matching_source_attestations(state, epoch) + if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) + ] ``` ```python @@ -1368,32 +1348,6 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) ``` -```python -def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]: - return [ - a for a in state.current_epoch_attestations - if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state))) - ] -``` - -```python -def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]: - return [ - a for a in state.previous_epoch_attestations - if a.data.target_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) - ] -``` - -```python -def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[PendingAttestation]: - return [ - a for a in state.previous_epoch_attestations - if a.data.beacon_block_root == get_block_root(state, a.data.slot) - ] -``` - -**Note**: Total balances computed for the previous epoch might be marginally different than the actual total balances during the previous epoch transition. Due to the tight bound on validator churn each epoch and small per-epoch rewards/penalties, the potential balance difference is very low and only marginally affects consensus safety. - ```python def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationData) -> Crosslink: return Crosslink( @@ -1404,9 +1358,8 @@ def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationDat ``` ```python -def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]: - pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations - shard_attestations = [a for a in pending_attestations if a.data.shard == shard] +def get_winning_crosslink_and_attesting_indices(state: BeaconState, shard: Shard, epoch: Epoch) -> Tuple[Crosslink, List[ValidatorIndex]]: + shard_attestations = [a for a in get_matching_source_attestations(state, epoch) if a.data.shard == shard] shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations] candidate_crosslinks = [ c for c in shard_crosslinks @@ -1441,6 +1394,8 @@ def process_justification_and_finalization(state: BeaconState) -> None: if get_current_epoch(state) <= GENESIS_EPOCH + 1: return + previous_epoch = get_previous_epoch(state) + current_epoch = get_current_epoch(state) old_previous_justified_epoch = state.previous_justified_epoch old_current_justified_epoch = state.current_justified_epoch @@ -1448,36 +1403,35 @@ def process_justification_and_finalization(state: BeaconState) -> None: state.previous_justified_epoch = state.current_justified_epoch state.previous_justified_root = state.current_justified_root state.justification_bitfield = (state.justification_bitfield << 1) % 2**64 - previous_boundary_attesting_balance = get_attesting_balance(state, get_previous_epoch_boundary_attestations(state)) - if previous_boundary_attesting_balance * 3 >= get_previous_total_balance(state) * 2: - state.current_justified_epoch = get_previous_epoch(state) - state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) + previous_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, previous_epoch)) + if previous_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: + state.current_justified_epoch = previous_epoch + state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 1) - current_boundary_attesting_balance = get_attesting_balance(state, get_current_epoch_boundary_attestations(state)) - if current_boundary_attesting_balance * 3 >= get_current_total_balance(state) * 2: - state.current_justified_epoch = get_current_epoch(state) - state.current_justified_root = get_block_root(state, get_epoch_start_slot(state.current_justified_epoch)) + current_epoch_matching_target_balance = get_attesting_balance(state, get_matching_target_attestations(state, current_epoch)) + if current_epoch_matching_target_balance * 3 >= get_total_active_balance(state) * 2: + state.current_justified_epoch = current_epoch + state.current_justified_root = get_block_root(state, state.current_justified_epoch) state.justification_bitfield |= (1 << 0) # Process finalizations bitfield = state.justification_bitfield - current_epoch = get_current_epoch(state) # The 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source if (bitfield >> 1) % 8 == 0b111 and old_previous_justified_epoch == current_epoch - 3: state.finalized_epoch = old_previous_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) # The 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source if (bitfield >> 1) % 4 == 0b11 and old_previous_justified_epoch == current_epoch - 2: state.finalized_epoch = old_previous_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) # The 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source if (bitfield >> 0) % 8 == 0b111 and old_current_justified_epoch == current_epoch - 2: state.finalized_epoch = old_current_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) # The 1st/2nd most recent epochs are justified, the 1st using the 2nd as source if (bitfield >> 0) % 4 == 0b11 and old_current_justified_epoch == current_epoch - 1: state.finalized_epoch = old_current_justified_epoch - state.finalized_root = get_block_root(state, get_epoch_start_slot(state.finalized_epoch)) + state.finalized_root = get_block_root(state, state.finalized_epoch) ``` #### Crosslinks @@ -1490,8 +1444,9 @@ def process_crosslinks(state: BeaconState) -> None: previous_epoch = get_previous_epoch(state) next_epoch = get_current_epoch(state) + 1 for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)): + epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee): state.current_crosslinks[shard] = winning_crosslink ``` @@ -1500,74 +1455,55 @@ def process_crosslinks(state: BeaconState) -> None: First, we define additional helpers: -```python -def get_base_reward_from_total_balance(state: BeaconState, total_balance: Gwei, index: ValidatorIndex) -> Gwei: - if total_balance == 0: - return 0 - - adjusted_quotient = integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT - return get_effective_balance(state, index) // adjusted_quotient // 5 -``` - ```python def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: - return get_base_reward_from_total_balance(state, get_previous_total_balance(state), index) + adjusted_quotient = integer_squareroot(get_total_active_balance(state)) // BASE_REWARD_QUOTIENT + if adjusted_quotient == 0: + return 0 + return state.validator_registry[index].effective_balance // adjusted_quotient // BASE_REWARDS_PER_EPOCH + ``` ```python -def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_since_finality: int) -> Gwei: - if epochs_since_finality <= 4: - extra_penalty = 0 - else: - extra_penalty = get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 - return get_base_reward(state, index) + extra_penalty -``` - -```python -def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - current_epoch = get_current_epoch(state) - epochs_since_finality = current_epoch + 1 - state.finalized_epoch +def get_attestation_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: + previous_epoch = get_previous_epoch(state) + total_balance = get_total_active_balance(state) rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] - # Some helper variables - boundary_attestations = get_previous_epoch_boundary_attestations(state) - boundary_attesting_balance = get_attesting_balance(state, boundary_attestations) - total_balance = get_previous_total_balance(state) - total_attesting_balance = get_attesting_balance(state, state.previous_epoch_attestations) - matching_head_attestations = get_previous_epoch_matching_head_attestations(state) - matching_head_balance = get_attesting_balance(state, matching_head_attestations) - eligible_validators = [ - index for index, validator in enumerate(state.validator_registry) - if ( - is_active_validator(validator, current_epoch) or - (validator.slashed and current_epoch < validator.withdrawable_epoch) - ) + eligible_validator_indices = [ + index for index, v in enumerate(state.validator_registry) + if is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch) ] - # Process rewards or penalties for all validators - for index in eligible_validators: - base_reward = get_base_reward(state, index) - # Expected FFG source - if index in get_unslashed_attesting_indices(state, state.previous_epoch_attestations): - rewards[index] += base_reward * total_attesting_balance // total_balance - # Inclusion speed bonus - earliest_attestation = get_earliest_attestation(state, state.previous_epoch_attestations, index) - inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot - rewards[index] += base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay - else: - penalties[index] += base_reward - # Expected FFG target - if index in get_unslashed_attesting_indices(state, boundary_attestations): - rewards[index] += base_reward * boundary_attesting_balance // total_balance - else: - penalties[index] += get_inactivity_penalty(state, index, epochs_since_finality) - # Expected head - if index in get_unslashed_attesting_indices(state, matching_head_attestations): - rewards[index] += base_reward * matching_head_balance // total_balance - else: - penalties[index] += base_reward - # Take away max rewards if we're not finalizing - if epochs_since_finality > 4: - penalties[index] += base_reward * 4 + + # Micro-incentives for matching FFG source, FFG target, and head + matching_source_attestations = get_matching_source_attestations(state, previous_epoch) + matching_target_attestations = get_matching_target_attestations(state, previous_epoch) + matching_head_attestations = get_matching_head_attestations(state, previous_epoch) + for attestations in (matching_source_attestations, matching_target_attestations, matching_head_attestations): + unslashed_attesting_indices = get_unslashed_attesting_indices(state, attestations) + attesting_balance = get_attesting_balance(state, attestations) + for index in eligible_validator_indices: + if index in unslashed_attesting_indices: + rewards[index] += get_base_reward(state, index) * attesting_balance // total_balance + else: + penalties[index] += get_base_reward(state, index) + + # Proposer and inclusion delay micro-rewards + for index in get_unslashed_attesting_indices(state, matching_source_attestations): + earliest_attestation = get_earliest_attestation(state, matching_source_attestations, index) + rewards[earliest_attestation.proposer_index] += get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT + inclusion_delay = earliest_attestation.inclusion_slot - earliest_attestation.data.slot + rewards[index] += get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_delay + + # Inactivity penalty + finality_delay = previous_epoch - state.finalized_epoch + if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY: + matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) + for index in eligible_validator_indices: + penalties[index] += BASE_REWARDS_PER_EPOCH * get_base_reward(state, index) + if index not in matching_target_attesting_indices: + penalties[index] += state.validator_registry[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT + return [rewards, penalties] ``` @@ -1576,15 +1512,17 @@ def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))): + epoch = slot_to_epoch(slot) for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): - winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard) + winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, shard, epoch) attesting_balance = get_total_balance(state, attesting_indices) committee_balance = get_total_balance(state, crosslink_committee) for index in crosslink_committee: + base_reward = get_base_reward(state, index) if index in attesting_indices: - rewards[index] += get_base_reward(state, index) * attesting_balance // committee_balance + rewards[index] += base_reward * attesting_balance // committee_balance else: - penalties[index] += get_base_reward(state, index) + penalties[index] += base_reward return [rewards, penalties] ``` @@ -1595,7 +1533,7 @@ def process_rewards_and_penalties(state: BeaconState) -> None: if get_current_epoch(state) == GENESIS_EPOCH: return - rewards1, penalties1 = get_justification_and_finalization_deltas(state) + rewards1, penalties1 = get_attestation_deltas(state) rewards2, penalties2 = get_crosslink_deltas(state) for i in range(len(state.validator_registry)): increase_balance(state, i, rewards1[i] + rewards2[i]) @@ -1610,21 +1548,22 @@ Run the following function: def process_registry_updates(state: BeaconState) -> None: # Process activation eligibility and ejections for index, validator in enumerate(state.validator_registry): - balance = get_balance(state, index) - if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and balance >= MAX_DEPOSIT_AMOUNT: + if validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance >= MAX_EFFECTIVE_BALANCE: validator.activation_eligibility_epoch = get_current_epoch(state) - if is_active_validator(validator, get_current_epoch(state)) and balance < EJECTION_BALANCE: + if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: initiate_validator_exit(state, index) - # Process activations + # Queue validators eligible for activation and not dequeued for activation prior to finalized epoch activation_queue = sorted([ index for index, validator in enumerate(state.validator_registry) if validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH and validator.activation_epoch >= get_delayed_activation_exit_epoch(state.finalized_epoch) ], key=lambda index: state.validator_registry[index].activation_eligibility_epoch) + # Dequeued validators for activation up to churn limit (without resetting activation epoch) for index in activation_queue[:get_churn_limit(state)]: - activate_validator(state, index) + if validator.activation_epoch == FAR_FUTURE_EPOCH: + validator.activation_epoch = get_delayed_activation_exit_epoch(get_current_epoch(state)) ``` #### Slashings @@ -1645,8 +1584,8 @@ def process_slashings(state: BeaconState) -> None: for index, validator in enumerate(state.validator_registry): if validator.slashed and current_epoch == validator.withdrawable_epoch - LATEST_SLASHED_EXIT_LENGTH // 2: penalty = max( - get_effective_balance(state, index) * min(total_penalties * 3, total_balance) // total_balance, - get_effective_balance(state, index) // MIN_PENALTY_QUOTIENT + validator.effective_balance * min(total_penalties * 3, total_balance) // total_balance, + validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT ) decrease_balance(state, index, penalty) ``` @@ -1660,8 +1599,14 @@ def process_final_updates(state: BeaconState) -> None: current_epoch = get_current_epoch(state) next_epoch = current_epoch + 1 # Reset eth1 data votes - if state.slot % SLOTS_PER_ETH1_VOTING_PERIOD == 0: + if (state.slot + 1) % SLOTS_PER_ETH1_VOTING_PERIOD == 0: state.eth1_data_votes = [] + # Update effective balances with hysteresis + for index, validator in enumerate(state.validator_registry): + balance = min(state.balances[index], MAX_EFFECTIVE_BALANCE) + HALF_INCREMENT = EFFECTIVE_BALANCE_INCREMENT // 2 + if balance < validator.effective_balance or validator.effective_balance + 3 * HALF_INCREMENT < balance: + validator.effective_balance = balance - balance % EFFECTIVE_BALANCE_INCREMENT # Update start shard state.latest_start_shard = (state.latest_start_shard + get_shard_delta(state, current_epoch)) % SHARD_COUNT # Set active index root @@ -1845,7 +1790,8 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: pending_attestation = PendingAttestation( data=data, aggregation_bitfield=attestation.aggregation_bitfield, - inclusion_slot=state.slot + inclusion_slot=state.slot, + proposer_index=get_beacon_proposer_index(state), ) if target_epoch == get_current_epoch(state): state.current_epoch_attestations.append(pending_attestation) @@ -1853,18 +1799,6 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: state.previous_epoch_attestations.append(pending_attestation) ``` -Run `process_proposer_attestation_rewards(state)`. - -```python -def process_proposer_attestation_rewards(state: BeaconState) -> None: - proposer_index = get_beacon_proposer_index(state) - for pending_attestations in (state.previous_epoch_attestations, state.current_epoch_attestations): - for index in get_unslashed_attesting_indices(state, pending_attestations): - if get_earliest_attestation(state, pending_attestations, index).inclusion_slot == state.slot: - base_reward = get_base_reward_from_total_balance(state, get_current_total_balance(state), index) - increase_balance(state, proposer_index, base_reward // PROPOSER_REWARD_QUOTIENT) -``` - ##### Deposits Verify that `len(block.body.deposits) == min(MAX_DEPOSITS, state.latest_eth1_data.deposit_count - state.deposit_index)`. @@ -1874,54 +1808,41 @@ For each `deposit` in `block.body.deposits`, run the following function: ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: """ - Process a deposit from Ethereum 1.0. - Used to add a validator or top up an existing validator's - balance by some ``deposit`` amount. - + Process an Eth1 deposit, registering a validator or increasing its balance. Note that this function mutates ``state``. """ - # Deposits must be processed in order - assert deposit.index == state.deposit_index - # Verify the Merkle branch - merkle_branch_is_valid = verify_merkle_branch( - leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialization + assert verify_merkle_branch( + leaf=hash_tree_root(deposit.data), proof=deposit.proof, depth=DEPOSIT_CONTRACT_TREE_DEPTH, index=deposit.index, root=state.latest_eth1_data.deposit_root, ) - assert merkle_branch_is_valid - # Increment the next deposit index we are expecting. Note that this - # needs to be done here because while the deposit contract will never - # create an invalid Merkle branch, it may admit an invalid deposit - # object, and we need to be able to skip over it + # Deposits must be processed in order + assert deposit.index == state.deposit_index state.deposit_index += 1 - validator_pubkeys = [v.pubkey for v in state.validator_registry] pubkey = deposit.data.pubkey amount = deposit.data.amount - + validator_pubkeys = [v.pubkey for v in state.validator_registry] if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) if not bls_verify(pubkey, signing_root(deposit.data), deposit.data.signature, get_domain(state, DOMAIN_DEPOSIT)): return - # Add new validator - validator = Validator( + # Add validator and balance entries + state.validator_registry.append(Validator( pubkey=pubkey, withdrawal_credentials=deposit.data.withdrawal_credentials, activation_eligibility_epoch=FAR_FUTURE_EPOCH, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - ) - - # Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled. - state.validator_registry.append(validator) - state.balances.append(0) - set_balance(state, len(state.validator_registry) - 1, amount) + effective_balance=amount - amount % EFFECTIVE_BALANCE_INCREMENT + )) + state.balances.append(amount) else: # Increase balance by deposit amount index = validator_pubkeys.index(pubkey) @@ -1958,8 +1879,6 @@ def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None: ##### Transfers -Note: Transfers are a temporary functionality for phases 0 and 1, to be removed in phase 2. - Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct. For each `transfer` in `block.body.transfers`, run the following function: @@ -1970,14 +1889,15 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: Process ``Transfer`` operation. Note that this function mutates ``state``. """ - # Verify the amount and fee aren't individually too big (for anti-overflow purposes) - assert get_balance(state, transfer.sender) >= max(transfer.amount, transfer.fee) + # Verify the amount and fee are not individually too big (for anti-overflow purposes) + assert state.balances[transfer.sender] >= max(transfer.amount, transfer.fee) # A transfer is valid in only one slot assert state.slot == transfer.slot - # Only withdrawn or not-yet-deposited accounts can transfer + # Sender must be not yet eligible for activation, withdrawn, or transfer balance over MAX_EFFECTIVE_BALANCE assert ( + state.validator_registry[transfer.sender].activation_eligibility_epoch == FAR_FUTURE_EPOCH or get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or - state.validator_registry[transfer.sender].activation_epoch == FAR_FUTURE_EPOCH + transfer.amount + transfer.fee + MAX_EFFECTIVE_BALANCE <= state.balances[transfer.sender] ) # Verify that the pubkey is valid assert ( @@ -1991,8 +1911,8 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None: increase_balance(state, transfer.recipient, transfer.amount) increase_balance(state, get_beacon_proposer_index(state), transfer.fee) # Verify balances are not dust - assert not (0 < get_balance(state, transfer.sender) < MIN_DEPOSIT_AMOUNT) - assert not (0 < get_balance(state, transfer.recipient) < MIN_DEPOSIT_AMOUNT) + assert not (0 < state.balances[transfer.sender] < MIN_DEPOSIT_AMOUNT) + assert not (0 < state.balances[transfer.recipient] < MIN_DEPOSIT_AMOUNT) ``` #### State root verification diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 41377ebd4..6fdb97067 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -8,6 +8,7 @@ - [Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice](#ethereum-20-phase-0----beacon-chain-fork-choice) - [Table of contents](#table-of-contents) - [Introduction](#introduction) + - [Prerequisites](#prerequisites) - [Constants](#constants) - [Time parameters](#time-parameters) - [Beacon chain processing](#beacon-chain-processing) @@ -19,6 +20,10 @@ This document represents is the specification for the beacon chain fork choice rule, part of Ethereum 2.0 phase 0. +## Prerequisites + +All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](./0_beacon-chain.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout. + ## Constants ### Time parameters diff --git a/specs/core/1_custody-game.md b/specs/core/1_custody-game.md index 74b086219..9f1c0e66a 100644 --- a/specs/core/1_custody-game.md +++ b/specs/core/1_custody-game.md @@ -28,9 +28,12 @@ - [`BeaconState`](#beaconstate) - [`BeaconBlockBody`](#beaconblockbody) - [Helpers](#helpers) + - [`typeof`](#typeof) + - [`empty`](#empty) - [`get_crosslink_chunk_count`](#get_crosslink_chunk_count) - [`get_custody_chunk_bit`](#get_custody_chunk_bit) - [`epoch_to_custody_period`](#epoch_to_custody_period) + - [`replace_empty_or_append`](#replace_empty_or_append) - [`verify_custody_key`](#verify_custody_key) - [Per-block processing](#per-block-processing) - [Operations](#operations) @@ -203,6 +206,14 @@ Add the following fields to the end of the specified container objects. Fields w ## Helpers +### `typeof` + +The `typeof` function accepts and SSZ object as a single input and returns the corresponding SSZ type. + +### `empty` + +The `empty` function accepts and SSZ type as input and returns an object of that type with all fields initialized to default values. + ### `get_crosslink_chunk_count` ```python @@ -229,6 +240,18 @@ def epoch_to_custody_period(epoch: Epoch) -> int: return epoch // EPOCHS_PER_CUSTODY_PERIOD ``` +### `replace_empty_or_append` + +```python +def replace_empty_or_append(list: List[Any], new_element: Any) -> int: + for i in range(len(list)): + if list[i] == empty(typeof(new_element)): + list[i] = new_element + return i + list.append(new_element) + return len(list) - 1 +``` + ### `verify_custody_key` ```python @@ -321,7 +344,7 @@ def process_chunk_challenge(state: BeaconState, depth = math.log2(next_power_of_two(get_custody_chunk_count(challenge.attestation))) assert challenge.chunk_index < 2**depth # Add new chunk challenge record - state.custody_chunk_challenge_records.append(CustodyChunkChallengeRecord( + new_record = CustodyChunkChallengeRecord( challenge_index=state.custody_challenge_index, challenger_index=get_beacon_proposer_index(state), responder_index=challenge.responder_index @@ -329,7 +352,9 @@ def process_chunk_challenge(state: BeaconState, crosslink_data_root=challenge.attestation.data.crosslink_data_root, depth=depth, chunk_index=challenge.chunk_index, - )) + ) + replace_empty_or_append(state.custody_chunk_challenge_records, new_record) + state.custody_challenge_index += 1 # Postpone responder withdrawability responder.withdrawable_epoch = FAR_FUTURE_EPOCH @@ -385,7 +410,7 @@ def process_bit_challenge(state: BeaconState, custody_bit = get_bitfield_bit(attestation.custody_bitfield, attesters.index(responder_index)) assert custody_bit != chunk_bits_xor # Add new bit challenge record - state.custody_bit_challenge_records.append(CustodyBitChallengeRecord( + new_record = CustodyBitChallengeRecord( challenge_index=state.custody_challenge_index, challenger_index=challenge.challenger_index, responder_index=challenge.responder_index, @@ -393,7 +418,8 @@ def process_bit_challenge(state: BeaconState, crosslink_data_root=challenge.attestation.crosslink_data_root, chunk_bits=challenge.chunk_bits, responder_key=challenge.responder_key, - )) + ) + replace_empty_or_append(state.custody_bit_challenge_records, new_record) state.custody_challenge_index += 1 # Postpone responder withdrawability responder.withdrawable_epoch = FAR_FUTURE_EPOCH @@ -434,7 +460,8 @@ def process_chunk_challenge_response(state: BeaconState, root=challenge.crosslink_data_root, ) # Clear the challenge - state.custody_chunk_challenge_records.remove(challenge) + records = state.custody_chunk_challenge_records + records[records.index(challenge)] = CustodyChunkChallengeRecord() # Reward the proposer proposer_index = get_beacon_proposer_index(state) increase_balance(state, proposer_index, base_reward(state, index) // MINOR_REWARD_QUOTIENT) @@ -457,7 +484,8 @@ def process_bit_challenge_response(state: BeaconState, # Verify the chunk bit does not match the challenge chunk bit assert get_custody_chunk_bit(challenge.responder_key, response.chunk) != get_bitfield_bit(challenge.chunk_bits, response.chunk_index) # Clear the challenge - state.custody_bit_challenge_records.remove(challenge) + records = state.custody_bit_challenge_records + records[records.index(challenge)] = CustodyBitChallengeRecord() # Slash challenger slash_validator(state, challenge.challenger_index, challenge.responder_index) ``` @@ -471,12 +499,14 @@ def process_challenge_deadlines(state: BeaconState) -> None: for challenge in state.custody_chunk_challenge_records: if get_current_epoch(state) > challenge.deadline: slash_validator(state, challenge.responder_index, challenge.challenger_index) - state.custody_chunk_challenge_records.remove(challenge) + records = state.custody_chunk_challenge_records + records[records.index(challenge)] = CustodyChunkChallengeRecord() for challenge in state.custody_bit_challenge_records: if get_current_epoch(state) > challenge.deadline: slash_validator(state, challenge.responder_index, challenge.challenger_index) - state.custody_bit_challenge_records.remove(challenge) + records = state.custody_bit_challenge_records + records[records.index(challenge)] = CustodyBitChallengeRecord() ``` In `process_penalties_and_exits`, change the definition of `eligible` to the following (note that it is not a pure function because `state` is declared in the surrounding scope): diff --git a/specs/light_client/sync_protocol.md b/specs/light_client/sync_protocol.md index 257590f4d..7cb1f6928 100644 --- a/specs/light_client/sync_protocol.md +++ b/specs/light_client/sync_protocol.md @@ -180,8 +180,8 @@ def verify_block_validity_proof(proof: BlockValidityProof, validator_memory: Val assert proof.shard_parent_block.beacon_chain_root == hash_tree_root(proof.header) committee = compute_committee(proof.header, validator_memory) # Verify that we have >=50% support - support_balance = sum([v.high_balance for i, v in enumerate(committee) if get_bitfield_bit(proof.shard_bitfield, i) is True]) - total_balance = sum([v.high_balance for i, v in enumerate(committee)]) + support_balance = sum([v.effective_balance for i, v in enumerate(committee) if get_bitfield_bit(proof.shard_bitfield, i) is True]) + total_balance = sum([v.effective_balance for i, v in enumerate(committee)]) assert support_balance * 2 > total_balance # Verify shard attestations group_public_key = bls_aggregate_pubkeys([ diff --git a/specs/networking/rpc-interface.md b/specs/networking/rpc-interface.md index 5d408b5a0..f1da8f7e3 100644 --- a/specs/networking/rpc-interface.md +++ b/specs/networking/rpc-interface.md @@ -247,7 +247,7 @@ Requests a list of block roots and slots from the peer. The `count` parameter MU Requests beacon block headers from the peer starting from `(start_root, start_slot)`. The response MUST contain no more than `max_headers` headers. `skip_slots` defines the maximum number of slots to skip between blocks. For example, requesting blocks starting at slots `2` a `skip_slots` value of `1` would return the blocks at `[2, 4, 6, 8, 10]`. In cases where a slot is empty for a given slot number, the closest previous block MUST be returned. For example, if slot `4` were empty in the previous example, the returned array would contain `[2, 3, 6, 8, 10]`. If slot three were further empty, the array would contain `[2, 6, 8, 10]` - i.e., duplicate blocks MUST be collapsed. A `skip_slots` value of `0` returns all blocks. -The function of the `skip_slots` parameter helps facilitate light client sync - for example, in [#459](https://github.com/ethereum/eth2.0-specs/issues/459) - and allows clients to balance the peers from whom they request headers. Clients could, for instance, request every 10th block from a set of peers where each per has a different starting block in order to populate block data. +The function of the `skip_slots` parameter helps facilitate light client sync - for example, in [#459](https://github.com/ethereum/eth2.0-specs/issues/459) - and allows clients to balance the peers from whom they request headers. Clients could, for instance, request every 10th block from a set of peers where each peer has a different starting block in order to populate block data. ### Beacon Block Bodies @@ -287,6 +287,6 @@ Requests the `block_bodies` associated with the provided `block_roots` from the **Response Body:** TBD -Requests contain the hashes of Merkle tree nodes that when merkelized yield the block's `state_root`. +Requests contain the hashes of Merkle tree nodes that when merkleized yield the block's `state_root`. The response will contain the values that, when hashed, yield the hashes inside the request body. diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index 6ccb8f22d..0080f2447 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -13,7 +13,7 @@ This is a **work in progress** describing typing, serialization and Merkleizatio - [Serialization](#serialization) - [`"uintN"`](#uintn) - [`"bool"`](#bool) - - [Vectors, containers, lists](#vectors-containers-lists) + - [Containers, vectors, lists](#containers-vectors-lists) - [Deserialization](#deserialization) - [Merkleization](#merkleization) - [Self-signed containers](#self-signed-containers) @@ -23,8 +23,9 @@ This is a **work in progress** describing typing, serialization and Merkleizatio | Name | Value | Description | |-|-|-| -| `BYTES_PER_CHUNK` | `32` | Number of bytes per chunk. -| `BYTES_PER_LENGTH_PREFIX` | `4` | Number of bytes per serialized length prefix. | +| `BYTES_PER_CHUNK` | `32` | Number of bytes per chunk. | +| `BYTES_PER_LENGTH_OFFSET` | `4` | Number of bytes per serialized length offset. | +| `BITS_PER_BYTE` | `8` | Number of bits per byte. | ## Typing ### Basic types @@ -59,7 +60,8 @@ The default value of a type upon initialization is recursively defined using `0` We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `"bytes"`. -*Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signing_root`, etc.) objects implicitly carry their type. +> *Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signing_root`, `is_variable_size`, etc.) objects implicitly carry their type. + ### `"uintN"` @@ -75,21 +77,24 @@ assert value in (True, False) return b"\x01" if value is True else b"\x00" ``` -### Vectors, containers, lists - -If `value` is fixed-size: +### Containers, vectors, lists ```python -return "".join([serialize(element) for element in value]) -``` +# Reccursively serialize +fixed_parts = [serialize(element) if not is_variable_size(element) else None for element in value] +variable_parts = [serialize(element) if is_variable_size(element) else b"" for element in value] -If `value` is variable-size: +# Compute and check lengths +fixed_lengths = [len(part) if part != None else BYTES_PER_LENGTH_OFFSET for part in fixed_parts] +variable_lengths = [len(part) for part in variable_parts] +assert sum(fixed_lengths + variable_lengths) < 2**(BYTES_PER_LENGTH_OFFSET * BITS_PER_BYTE) -```python -serialized_bytes = "".join([serialize(element) for element in value]) -assert len(serialized_bytes) < 2**(8 * BYTES_PER_LENGTH_PREFIX) -serialized_length = len(serialized_bytes).to_bytes(BYTES_PER_LENGTH_PREFIX, "little") -return serialized_length + serialized_bytes +# 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))] +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 +return b"".join(fixed_parts + variable_parts) ``` ## Deserialization diff --git a/specs/test_formats/README.md b/specs/test_formats/README.md index da2e38c01..273659ce9 100644 --- a/specs/test_formats/README.md +++ b/specs/test_formats/README.md @@ -118,7 +118,7 @@ Separation of configuration and tests aims to: Note: Some clients prefer compile-time constants and optimizations. They should compile for each configuration once, and run the corresponding tests per build target. -The format is described in `configs/constant_presets`. +The format is described in [`configs/constant_presets`](../../configs/constant_presets/README.md#format). ## Fork-timeline @@ -129,7 +129,7 @@ A fork timeline is (preferably) loaded in as a configuration object into a clien - we may decide on an epoch number for a fork based on external events (e.g. Eth1 log event), a client should be able to activate a fork dynamically. -The format is described in `configs/fork_timelines`. +The format is described in [`configs/fork_timelines`](../../configs/fork_timelines/README.md#format). ## Config sourcing diff --git a/specs/test_formats/bls/msg_hash_g2_compressed.md b/specs/test_formats/bls/msg_hash_g2_compressed.md index 4e194e90b..2feeb92ba 100644 --- a/specs/test_formats/bls/msg_hash_g2_compressed.md +++ b/specs/test_formats/bls/msg_hash_g2_compressed.md @@ -11,7 +11,7 @@ input: output: List[bytes48] -- length of two ``` -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x` +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. ## Condition diff --git a/specs/test_formats/bls/msg_hash_g2_uncompressed.md b/specs/test_formats/bls/msg_hash_g2_uncompressed.md index f42ea9998..792fe1f03 100644 --- a/specs/test_formats/bls/msg_hash_g2_uncompressed.md +++ b/specs/test_formats/bls/msg_hash_g2_uncompressed.md @@ -11,7 +11,7 @@ input: output: List[List[bytes48]] -- 3 lists, each a length of two ``` -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x` +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. ## Condition diff --git a/specs/test_formats/bls/priv_to_pub.md b/specs/test_formats/bls/priv_to_pub.md index 7af148d0f..ef62241ae 100644 --- a/specs/test_formats/bls/priv_to_pub.md +++ b/specs/test_formats/bls/priv_to_pub.md @@ -9,7 +9,7 @@ input: bytes32 -- the private key output: bytes48 -- the public key ``` -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x` +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. ## Condition diff --git a/specs/test_formats/bls/sign_msg.md b/specs/test_formats/bls/sign_msg.md index dd93174f2..9916f2cc2 100644 --- a/specs/test_formats/bls/sign_msg.md +++ b/specs/test_formats/bls/sign_msg.md @@ -12,7 +12,7 @@ input: output: bytes96 -- expected signature ``` -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x` +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. ## Condition diff --git a/specs/test_formats/ssz_static/core.md b/specs/test_formats/ssz_static/core.md index 059f11027..1d470c338 100644 --- a/specs/test_formats/ssz_static/core.md +++ b/specs/test_formats/ssz_static/core.md @@ -1,6 +1,6 @@ # Test format: SSZ static types -The goal of this type is to provide clients with a solid reference how the known SSZ objects should be encoded. +The goal of this type is to provide clients with a solid reference for how the known SSZ objects should be encoded. Each object described in the Phase-0 spec is covered. This is important, as many of the clients aiming to serialize/deserialize objects directly into structs/classes do not support (or have alternatives for) generic SSZ encoding/decoding. @@ -27,6 +27,6 @@ A test-runner can implement the following assertions: ## References -**`serialized`**: [SSZ serialization](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/simple-serialize.md#serialization) -**`root`** - [hash_tree_root](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/simple-serialize.md#merkleization) function -**`signing_root`** - [signing_root](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/simple-serialize.md#self-signed-containers) function +**`serialized`**: [SSZ serialization](../../simple-serialize.md#serialization) +**`root`** - [hash_tree_root](../../simple-serialize.md#merkleization) function +**`signing_root`** - [signing_root](../../simple-serialize.md#self-signed-containers) function diff --git a/specs/validator/0_beacon-chain-validator.md b/specs/validator/0_beacon-chain-validator.md index cb19097dd..dab02b773 100644 --- a/specs/validator/0_beacon-chain-validator.md +++ b/specs/validator/0_beacon-chain-validator.md @@ -66,7 +66,7 @@ A validator is an entity that participates in the consensus of the Ethereum 2.0 ## Prerequisites -All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](../core/0_beacon-chain.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout. +All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](../core/0_beacon-chain.md) and [Phase 0 -- Deposit Contract](../core/0_deposit-contract.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout. ## Constants @@ -96,16 +96,15 @@ The validator constructs their `withdrawal_credentials` via the following: ### Submit deposit -In phase 0, all incoming validator deposits originate from the Ethereum 1.0 PoW chain. Deposits are made to the [deposit contract](../core/0_beacon-chain.md#ethereum-10-deposit-contract) located at `DEPOSIT_CONTRACT_ADDRESS`. +In phase 0, all incoming validator deposits originate from the Ethereum 1.0 PoW chain. Deposits are made to the [deposit contract](../core/0_deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. To submit a deposit: * Pack the validator's [initialization parameters](#initialization) into `deposit_data`, a [`DepositData`](../core/0_beacon-chain.md#depositdata) SSZ object. -* Let `proof_of_possession` be the result of `bls_sign` of the `signing_root(deposit_data)` with `domain=DOMAIN_DEPOSIT`. -* Set `deposit_data.proof_of_possession = proof_of_possession`. * Let `amount` be the amount in Gwei to be deposited by the validator where `MIN_DEPOSIT_AMOUNT <= amount <= MAX_DEPOSIT_AMOUNT`. * Set `deposit_data.amount = amount`. -* Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `deposit(deposit_input: bytes[512])` along with `serialize(deposit_data)` as the singular `bytes` input along with a deposit of `amount` Gwei. +* Let `signature` be the result of `bls_sign` of the `signing_root(deposit_data)` with `domain=DOMAIN_DEPOSIT`. +* Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])` along with a deposit of `amount` Gwei. _Note_: Deposits made for the same `pubkey` are treated as for the same validator. A singular `Validator` will be added to `state.validator_registry` with each additional deposit amount added to the validator's balance. A validator can only be activated when total deposits for the validator pubkey meet or exceed `MAX_DEPOSIT_AMOUNT`. @@ -139,7 +138,7 @@ A validator has two primary responsibilities to the beacon chain -- [proposing b ### Block proposal -A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `get_beacon_proposer_index(state, slot)` returns the validator's `validator_index`. 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 is to create, sign, and broadcast a `block` that is a child of `parent` and that executes a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function). +A validator is expected to propose a [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) at the beginning of any slot during which `get_beacon_proposer_index(state)` returns the validator's `validator_index`. 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 is to create, sign, and broadcast a `block` that is a child of `parent` and that executes a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function). There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312500 validators = 10 million ETH, that's once per ~3 weeks). @@ -225,7 +224,7 @@ Up to `MAX_ATTESTATIONS` aggregate attestations can be included in the `block`. ##### Deposits -If there are any unprocessed deposits for the existing `state.latest_eth1_data` (i.e. `state.latest_eth1_data.deposit_count > state.deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, latest_eth1_data.deposit_count - state.deposit_index)`. These [`deposits`](../core/0_beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth1.0 deposit contract](../core/0_beacon-chain.md#ethereum-10-deposit-contract) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](../core/0_beacon-chain.md#deposits). +If there are any unprocessed deposits for the existing `state.latest_eth1_data` (i.e. `state.latest_eth1_data.deposit_count > state.deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, latest_eth1_data.deposit_count - state.deposit_index)`. These [`deposits`](../core/0_beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth1.0 deposit contract](../core/0_deposit-contract.md) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](../core/0_beacon-chain.md#deposits). The `proof` for each deposit must be constructed against the deposit root contained in `state.latest_eth1_data` rather than the deposit root at the time the deposit was initially logged from the 1.0 chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `latest_eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation. @@ -369,7 +368,7 @@ def get_committee_assignment( return assignment ``` -A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run during the slot in question and can not reliably be used to predict in advance. +A validator can use the following function to see if they are supposed to propose during their assigned committee slot. This function can only be run during the slot in question. Proposer selection is only stable within the context of the current epoch. ```python def is_proposer_at_slot(state: BeaconState, diff --git a/test_generators/README.md b/test_generators/README.md index 743157aae..94db105c0 100644 --- a/test_generators/README.md +++ b/test_generators/README.md @@ -28,9 +28,12 @@ make clean This runs all the generators. ```bash -make gen_yaml_tests +make -j 4 gen_yaml_tests ``` +The `-j N` flag makes the generators run in parallel, with `N` being the amount of cores. + + ### Running a single generator The make file auto-detects generators in the `test_generators/` directory, @@ -69,7 +72,7 @@ Note: make sure to run `make pyspec` from the root of the specs repository, to b Install all the necessary requirements (re-run when you add more): ```bash -pip3 install -r requirements.txt +pip3 install -e .[pyspec] ``` And write your initial test generator, extending the base generator: diff --git a/test_generators/operations/deposits.py b/test_generators/operations/deposits.py index 454c6f22d..bd523abe4 100644 --- a/test_generators/operations/deposits.py +++ b/test_generators/operations/deposits.py @@ -29,7 +29,7 @@ def build_deposit_data(state, message_hash=signing_root(deposit_data), privkey=privkey, domain=spec.get_domain( - state.fork, + state, spec.get_current_epoch(state), spec.DOMAIN_DEPOSIT, ) @@ -46,7 +46,7 @@ def build_deposit(state, deposit_data = build_deposit_data(state, pubkey, withdrawal_cred, privkey, amount) - item = spec.hash(deposit_data.serialize()) + item = deposit_data.hash_tree_root() index = len(deposit_data_leaves) deposit_data_leaves.append(item) tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves)) @@ -69,7 +69,7 @@ def build_deposit_for_index(initial_validator_count: int, index: int) -> Tuple[s ) state = genesis.create_genesis_state(genesis_deposits) - deposit_data_leaves = [spec.hash(dep.data.serialize()) for dep in genesis_deposits] + deposit_data_leaves = [dep.data.hash_tree_root() for dep in genesis_deposits] deposit = build_deposit( state, diff --git a/test_generators/operations/genesis.py b/test_generators/operations/genesis.py index 7e0146f67..decb822f8 100644 --- a/test_generators/operations/genesis.py +++ b/test_generators/operations/genesis.py @@ -4,7 +4,7 @@ from typing import List def create_genesis_state(deposits: List[spec.Deposit]) -> spec.BeaconState: - deposit_root = get_merkle_root((tuple([spec.hash(dep.data.serialize()) for dep in deposits]))) + deposit_root = get_merkle_root((tuple([(dep.data.hash_tree_root()) for dep in deposits]))) return spec.get_genesis_beacon_state( deposits, @@ -32,7 +32,7 @@ def create_deposits(pubkeys: List[spec.BLSPubkey], withdrawal_cred: List[spec.By ] # Fill tree with existing deposits - deposit_data_leaves = [spec.hash(data.serialize()) for data in deposit_data] + deposit_data_leaves = [data.hash_tree_root() for data in deposit_data] tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves)) return [ diff --git a/test_generators/ssz_static/README.md b/test_generators/ssz_static/README.md index d73556e1b..453d6d0e5 100644 --- a/test_generators/ssz_static/README.md +++ b/test_generators/ssz_static/README.md @@ -1,6 +1,6 @@ # SSZ-static The purpose of this test-generator is to provide test-vectors for the most important applications of SSZ: - the serialization and hashing of ETH 2.0 data types + the serialization and hashing of ETH 2.0 data types. Test-format documentation can be found [here](../../specs/test_formats/ssz_static/README.md). diff --git a/test_libs/config_helpers/requirements.txt b/test_libs/config_helpers/requirements.txt deleted file mode 100644 index e441a474b..000000000 --- a/test_libs/config_helpers/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -ruamel.yaml==0.15.87 diff --git a/test_libs/config_helpers/setup.py b/test_libs/config_helpers/setup.py index 90ad94ee4..88669c092 100644 --- a/test_libs/config_helpers/setup.py +++ b/test_libs/config_helpers/setup.py @@ -1,9 +1,20 @@ -from distutils.core import setup +from setuptools import setup, find_packages + + +deps = { + 'preset_loader': [ + "ruamel.yaml==0.15.87", + ], +} + +deps['dev'] = ( + deps['preset_loader'] +) + +install_requires = deps['preset_loader'] setup( name='config_helpers', - packages=['preset_loader'], - install_requires=[ - "ruamel.yaml==0.15.87" - ] + packages=find_packages(exclude=["tests", "tests.*"]), + install_requires=install_requires, ) diff --git a/test_libs/gen_helpers/requirements.txt b/test_libs/gen_helpers/requirements.txt deleted file mode 100644 index 3d6a39458..000000000 --- a/test_libs/gen_helpers/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -ruamel.yaml==0.15.87 -eth-utils==1.4.1 diff --git a/test_libs/gen_helpers/setup.py b/test_libs/gen_helpers/setup.py index 5de27a6db..0c84ff2e2 100644 --- a/test_libs/gen_helpers/setup.py +++ b/test_libs/gen_helpers/setup.py @@ -1,10 +1,21 @@ -from distutils.core import setup +from setuptools import setup, find_packages + + +deps = { + 'gen_base': [ + "ruamel.yaml==0.15.87", + "eth-utils==1.4.1", + ], +} + +deps['dev'] = ( + deps['gen_base'] +) + +install_requires = deps['gen_base'] setup( name='gen_helpers', - packages=['gen_base'], - install_requires=[ - "ruamel.yaml==0.15.87", - "eth-utils==1.4.1" - ] + packages=find_packages(exclude=["tests", "tests.*"]), + install_requires=install_requires, ) diff --git a/test_libs/pyspec/README.md b/test_libs/pyspec/README.md index 20c01bde4..ab2967024 100644 --- a/test_libs/pyspec/README.md +++ b/test_libs/pyspec/README.md @@ -38,7 +38,7 @@ Install dependencies: ```bash python3 -m venv venv . venv/bin/activate -pip3 install -r requirements.txt +pip3 install -e .[dev] ``` Note: make sure to run `make -B pyspec` from the root of the specs repository, to build the parts of the pyspec module derived from the markdown specs. @@ -46,7 +46,7 @@ The `-B` flag may be helpful to force-overwrite the `pyspec` output after you ma Run the tests: ``` -pytest -m minimal_config . +pytest --config=minimal ``` @@ -58,4 +58,4 @@ The pyspec is not a replacement. ## License -Same as the spec itself, see LICENSE file in spec repository root. +Same as the spec itself, see [LICENSE](../../LICENSE) file in spec repository root. diff --git a/test_libs/pyspec/eth2spec/phase0/state_transition.py b/test_libs/pyspec/eth2spec/phase0/state_transition.py index 38ecd2a02..1bef358d4 100644 --- a/test_libs/pyspec/eth2spec/phase0/state_transition.py +++ b/test_libs/pyspec/eth2spec/phase0/state_transition.py @@ -11,7 +11,6 @@ from .spec import ( BeaconState, BeaconBlock, Slot, - process_proposer_attestation_rewards, ) @@ -52,7 +51,6 @@ def process_operations(state: BeaconState, block: BeaconBlock) -> None: spec.MAX_ATTESTATIONS, spec.process_attestation, ) - process_proposer_attestation_rewards(state) assert len(block.body.deposits) == expected_deposit_count(state) process_operation_type( diff --git a/test_libs/pyspec/requirements.txt b/test_libs/pyspec/requirements.txt deleted file mode 100644 index 3296ef807..000000000 --- a/test_libs/pyspec/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -eth-utils>=1.3.0,<2 -eth-typing>=2.1.0,<3.0.0 -pycryptodome==3.7.3 -py_ecc>=1.6.0 -pytest>=3.6,<3.7 diff --git a/test_libs/pyspec/setup.py b/test_libs/pyspec/setup.py index 1a131a417..3fd9d4c0f 100644 --- a/test_libs/pyspec/setup.py +++ b/test_libs/pyspec/setup.py @@ -1,13 +1,28 @@ from setuptools import setup, find_packages -setup( - name='pyspec', - packages=find_packages(), - tests_require=["pytest"], - install_requires=[ + +deps = { + 'pyspec': [ "eth-utils>=1.3.0,<2", "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.7.3", "py_ecc>=1.6.0", - ] + ], + 'test': [ + "pytest>=3.6,<3.7", + ], +} + +deps['dev'] = ( + deps['pyspec'] + + deps['test'] +) + +install_requires = deps['pyspec'] + +setup( + name='pyspec', + packages=find_packages(exclude=["tests", "tests.*"]), + install_requires=install_requires, + extras_require=deps, ) diff --git a/test_libs/pyspec/tests/README.md b/test_libs/pyspec/tests/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py index 84c19145a..bcaf6fb7a 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py +++ b/test_libs/pyspec/tests/block_processing/test_process_attester_slashing.py @@ -3,11 +3,11 @@ import pytest import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( - get_balance, get_beacon_proposer_index, process_attester_slashing, ) from tests.helpers import ( + get_balance, get_valid_attester_slashing, next_epoch, ) diff --git a/test_libs/pyspec/tests/block_processing/test_process_deposit.py b/test_libs/pyspec/tests/block_processing/test_process_deposit.py index 4031e650d..bbfb390ef 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_deposit.py +++ b/test_libs/pyspec/tests/block_processing/test_process_deposit.py @@ -4,11 +4,11 @@ import pytest import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( - get_balance, ZERO_HASH, process_deposit, ) from tests.helpers import ( + get_balance, build_deposit, privkeys, pubkeys, @@ -32,7 +32,7 @@ def test_success(state): deposit_data_leaves, pubkey, privkey, - spec.MAX_DEPOSIT_AMOUNT, + spec.MAX_EFFECTIVE_BALANCE, ) pre_state.latest_eth1_data.deposit_root = root @@ -45,7 +45,7 @@ def test_success(state): assert len(post_state.validator_registry) == len(state.validator_registry) + 1 assert len(post_state.balances) == len(state.balances) + 1 assert post_state.validator_registry[index].pubkey == pubkeys[index] - assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT + assert get_balance(post_state, index) == spec.MAX_EFFECTIVE_BALANCE assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count return pre_state, deposit, post_state @@ -56,7 +56,7 @@ def test_success_top_up(state): deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) validator_index = 0 - amount = spec.MAX_DEPOSIT_AMOUNT // 4 + amount = spec.MAX_EFFECTIVE_BALANCE // 4 pubkey = pubkeys[validator_index] privkey = privkeys[validator_index] deposit, root, deposit_data_leaves = build_deposit( @@ -95,7 +95,7 @@ def test_wrong_index(state): deposit_data_leaves, pubkey, privkey, - spec.MAX_DEPOSIT_AMOUNT, + spec.MAX_EFFECTIVE_BALANCE, ) # mess up deposit_index @@ -124,7 +124,7 @@ def test_bad_merkle_proof(state): deposit_data_leaves, pubkey, privkey, - spec.MAX_DEPOSIT_AMOUNT, + spec.MAX_EFFECTIVE_BALANCE, ) # mess up merkle branch diff --git a/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py b/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py index 6d5f3045d..475221036 100644 --- a/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py +++ b/test_libs/pyspec/tests/block_processing/test_process_proposer_slashing.py @@ -3,11 +3,11 @@ import pytest import eth2spec.phase0.spec as spec from eth2spec.phase0.spec import ( - get_balance, get_current_epoch, process_proposer_slashing, ) from tests.helpers import ( + get_balance, get_valid_proposer_slashing, ) diff --git a/test_libs/pyspec/tests/block_processing/test_process_transfer.py b/test_libs/pyspec/tests/block_processing/test_process_transfer.py new file mode 100644 index 000000000..0eeaa7792 --- /dev/null +++ b/test_libs/pyspec/tests/block_processing/test_process_transfer.py @@ -0,0 +1,141 @@ +from copy import deepcopy +import pytest + +import eth2spec.phase0.spec as spec + +from eth2spec.phase0.spec import ( + get_active_validator_indices, + get_beacon_proposer_index, + get_current_epoch, + process_transfer, +) +from tests.helpers import ( + get_valid_transfer, + next_epoch, +) + + +# mark entire file as 'transfers' +pytestmark = pytest.mark.transfers + + +def run_transfer_processing(state, transfer, valid=True): + """ + Run ``process_transfer`` returning the pre and post state. + If ``valid == False``, run expecting ``AssertionError`` + """ + post_state = deepcopy(state) + + if not valid: + with pytest.raises(AssertionError): + process_transfer(post_state, transfer) + return state, None + + + process_transfer(post_state, transfer) + + proposer_index = get_beacon_proposer_index(state) + pre_transfer_sender_balance = state.balances[transfer.sender] + pre_transfer_recipient_balance = state.balances[transfer.recipient] + pre_transfer_proposer_balance = state.balances[proposer_index] + sender_balance = post_state.balances[transfer.sender] + recipient_balance = post_state.balances[transfer.recipient] + assert sender_balance == pre_transfer_sender_balance - transfer.amount - transfer.fee + assert recipient_balance == pre_transfer_recipient_balance + transfer.amount + assert post_state.balances[proposer_index] == pre_transfer_proposer_balance + transfer.fee + + return state, post_state + + +def test_success_non_activated(state): + transfer = get_valid_transfer(state) + # un-activate so validator can transfer + state.validator_registry[transfer.sender].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH + + pre_state, post_state = run_transfer_processing(state, transfer) + + return pre_state, transfer, post_state + + +def test_success_withdrawable(state): + next_epoch(state) + + transfer = get_valid_transfer(state) + + # withdrawable_epoch in past so can transfer + state.validator_registry[transfer.sender].withdrawable_epoch = get_current_epoch(state) - 1 + + pre_state, post_state = run_transfer_processing(state, transfer) + + return pre_state, transfer, post_state + + +def test_success_active_above_max_effective(state): + sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] + amount = spec.MAX_EFFECTIVE_BALANCE // 32 + state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + amount + transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0) + + pre_state, post_state = run_transfer_processing(state, transfer) + + return pre_state, transfer, post_state + + +def test_active_but_transfer_past_effective_balance(state): + sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] + amount = spec.MAX_EFFECTIVE_BALANCE // 32 + state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount, fee=0) + + pre_state, post_state = run_transfer_processing(state, transfer, False) + + return pre_state, transfer, post_state + + +def test_incorrect_slot(state): + transfer = get_valid_transfer(state, slot=state.slot+1) + # un-activate so validator can transfer + state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH + + pre_state, post_state = run_transfer_processing(state, transfer, False) + + return pre_state, transfer, post_state + + +def test_insufficient_balance(state): + sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] + amount = spec.MAX_EFFECTIVE_BALANCE + state.balances[sender_index] = spec.MAX_EFFECTIVE_BALANCE + transfer = get_valid_transfer(state, sender_index=sender_index, amount=amount + 1, fee=0) + + # un-activate so validator can transfer + state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH + + pre_state, post_state = run_transfer_processing(state, transfer, False) + + return pre_state, transfer, post_state + + +def test_no_dust(state): + sender_index = get_active_validator_indices(state, get_current_epoch(state))[-1] + balance = state.balances[sender_index] + transfer = get_valid_transfer(state, sender_index=sender_index, amount=balance - spec.MIN_DEPOSIT_AMOUNT + 1, fee=0) + + # un-activate so validator can transfer + state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH + + pre_state, post_state = run_transfer_processing(state, transfer, False) + + return pre_state, transfer, post_state + + +def test_invalid_pubkey(state): + transfer = get_valid_transfer(state) + state.validator_registry[transfer.sender].withdrawal_credentials = spec.ZERO_HASH + + # un-activate so validator can transfer + state.validator_registry[transfer.sender].activation_epoch = spec.FAR_FUTURE_EPOCH + + pre_state, post_state = run_transfer_processing(state, transfer, False) + + return pre_state, transfer, post_state diff --git a/test_libs/pyspec/tests/conftest.py b/test_libs/pyspec/tests/conftest.py index bf9b1009b..9840dc7b2 100644 --- a/test_libs/pyspec/tests/conftest.py +++ b/test_libs/pyspec/tests/conftest.py @@ -1,63 +1,29 @@ import pytest from eth2spec.phase0 import spec +from preset_loader import loader from .helpers import ( create_genesis_state, ) -DEFAULT_CONFIG = {} # no change - -MINIMAL_CONFIG = { - "SHARD_COUNT": 8, - "MIN_ATTESTATION_INCLUSION_DELAY": 2, - "TARGET_COMMITTEE_SIZE": 4, - "SLOTS_PER_EPOCH": 8, - "SLOTS_PER_HISTORICAL_ROOT": 64, - "LATEST_RANDAO_MIXES_LENGTH": 64, - "LATEST_ACTIVE_INDEX_ROOTS_LENGTH": 64, - "LATEST_SLASHED_EXIT_LENGTH": 64, -} - - -def overwrite_spec_config(config): - for field in config: - setattr(spec, field, config[field]) - if field == "LATEST_RANDAO_MIXES_LENGTH": - spec.BeaconState.fields['latest_randao_mixes'][1] = config[field] - elif field == "SHARD_COUNT": - spec.BeaconState.fields['current_crosslinks'][1] = config[field] - spec.BeaconState.fields['previous_crosslinks'][1] = config[field] - elif field == "SLOTS_PER_HISTORICAL_ROOT": - spec.BeaconState.fields['latest_block_roots'][1] = config[field] - spec.BeaconState.fields['latest_state_roots'][1] = config[field] - spec.HistoricalBatch.fields['block_roots'][1] = config[field] - spec.HistoricalBatch.fields['state_roots'][1] = config[field] - elif field == "LATEST_ACTIVE_INDEX_ROOTS_LENGTH": - spec.BeaconState.fields['latest_active_index_roots'][1] = config[field] - elif field == "LATEST_SLASHED_EXIT_LENGTH": - spec.BeaconState.fields['latest_slashed_balances'][1] = config[field] - - -@pytest.fixture( - params=[ - pytest.param(MINIMAL_CONFIG, marks=pytest.mark.minimal_config), - DEFAULT_CONFIG, - ] -) -def config(request): - return request.param +def pytest_addoption(parser): + parser.addoption( + "--config", action="store", default="minimal", help="config: make the pyspec use the specified configuration" + ) @pytest.fixture(autouse=True) -def overwrite_config(config): - overwrite_spec_config(config) +def config(request): + config_name = request.config.getoption("--config") + presets = loader.load_presets('../../configs/', config_name) + spec.apply_constants_preset(presets) @pytest.fixture -def num_validators(): - return 100 +def num_validators(config): + return spec.SLOTS_PER_EPOCH * 8 @pytest.fixture diff --git a/test_libs/pyspec/tests/helpers.py b/test_libs/pyspec/tests/helpers.py index be43ac6aa..63e4cd710 100644 --- a/test_libs/pyspec/tests/helpers.py +++ b/test_libs/pyspec/tests/helpers.py @@ -21,12 +21,14 @@ from eth2spec.phase0.spec import ( DepositData, Eth1Data, ProposerSlashing, + Transfer, VoluntaryExit, # functions convert_to_indexed, get_active_validator_indices, get_attesting_indices, get_block_root, + get_block_root_at_slot, get_crosslink_committees_at_slot, get_current_epoch, get_domain, @@ -46,11 +48,15 @@ from eth2spec.utils.merkle_minimal import ( ) -privkeys = [i + 1 for i in range(1000)] +privkeys = [i + 1 for i in range(1024)] pubkeys = [bls.privtopub(privkey) for privkey in privkeys] pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} +def get_balance(state, index): + return state.balances[index] + + def set_bitfield_bit(bitfield, i): """ Set the bit in ``bitfield`` at position ``i`` to ``1``. @@ -76,10 +82,10 @@ def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=N pubkey=pubkey, # insecurely use pubkey as withdrawal key as well withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:], - amount=spec.MAX_DEPOSIT_AMOUNT, + amount=spec.MAX_EFFECTIVE_BALANCE, signature=signature, ) - item = hash(deposit_data.serialize()) + item = deposit_data.hash_tree_root() deposit_data_leaves.append(item) tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves)) root = get_merkle_root((tuple(deposit_data_leaves))) @@ -116,6 +122,7 @@ def create_genesis_state(num_validators, deposit_data_leaves=None): def build_empty_block_for_next_slot(state): empty_block = BeaconBlock() empty_block.slot = state.slot + 1 + empty_block.body.eth1_data.deposit_count = state.deposit_index previous_block_header = deepcopy(state.latest_block_header) if previous_block_header.state_root == spec.ZERO_HASH: previous_block_header.state_root = state.hash_tree_root() @@ -148,16 +155,15 @@ def build_attestation_data(state, slot, shard): if slot == state.slot: block_root = build_empty_block_for_next_slot(state).previous_block_root else: - block_root = get_block_root(state, slot) + block_root = get_block_root_at_slot(state, slot) current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state)) if slot < current_epoch_start_slot: - print(slot) - epoch_boundary_root = get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) + epoch_boundary_root = get_block_root(state, get_previous_epoch(state)) elif slot == current_epoch_start_slot: epoch_boundary_root = block_root else: - epoch_boundary_root = get_block_root(state, current_epoch_start_slot) + epoch_boundary_root = get_block_root(state, get_current_epoch(state)) if slot < current_epoch_start_slot: justified_epoch = state.previous_justified_epoch @@ -204,7 +210,7 @@ def build_deposit(state, amount): deposit_data = build_deposit_data(state, pubkey, privkey, amount) - item = hash(deposit_data.serialize()) + item = deposit_data.hash_tree_root() index = len(deposit_data_leaves) deposit_data_leaves.append(item) tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves)) @@ -323,6 +329,48 @@ def get_valid_attestation(state, slot=None): return attestation +def get_valid_transfer(state, slot=None, sender_index=None, amount=None, fee=None): + if slot is None: + slot = state.slot + current_epoch = get_current_epoch(state) + if sender_index is None: + sender_index = get_active_validator_indices(state, current_epoch)[-1] + recipient_index = get_active_validator_indices(state, current_epoch)[0] + transfer_pubkey = pubkeys[-1] + transfer_privkey = privkeys[-1] + + if fee is None: + fee = get_balance(state, sender_index) // 32 + if amount is None: + amount = get_balance(state, sender_index) - fee + + transfer = Transfer( + sender=sender_index, + recipient=recipient_index, + amount=amount, + fee=fee, + slot=slot, + pubkey=transfer_pubkey, + signature=ZERO_HASH, + ) + transfer.signature = bls.sign( + message_hash=signing_root(transfer), + privkey=transfer_privkey, + domain=get_domain( + state=state, + domain_type=spec.DOMAIN_TRANSFER, + message_epoch=get_current_epoch(state), + ) + ) + + # ensure withdrawal_credentials reproducable + state.validator_registry[transfer.sender].withdrawal_credentials = ( + spec.BLS_WITHDRAWAL_PREFIX_BYTE + spec.hash(transfer.pubkey)[1:] + ) + + return transfer + + def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0): message_hash = AttestationDataAndCustodyBit( data=attestation_data, diff --git a/test_libs/pyspec/tests/test_finality.py b/test_libs/pyspec/tests/test_finality.py new file mode 100644 index 000000000..ca048c2b2 --- /dev/null +++ b/test_libs/pyspec/tests/test_finality.py @@ -0,0 +1,198 @@ +from copy import deepcopy + +import pytest + +import eth2spec.phase0.spec as spec + +from eth2spec.phase0.state_transition import ( + state_transition, +) +from .helpers import ( + build_empty_block_for_next_slot, + fill_aggregate_attestation, + get_current_epoch, + get_epoch_start_slot, + get_valid_attestation, + next_epoch, +) + +# mark entire file as 'state' +pytestmark = pytest.mark.state + + +def check_finality(state, + prev_state, + current_justified_changed, + previous_justified_changed, + finalized_changed): + if current_justified_changed: + assert state.current_justified_epoch > prev_state.current_justified_epoch + assert state.current_justified_root != prev_state.current_justified_root + else: + assert state.current_justified_epoch == prev_state.current_justified_epoch + assert state.current_justified_root == prev_state.current_justified_root + + if previous_justified_changed: + assert state.previous_justified_epoch > prev_state.previous_justified_epoch + assert state.previous_justified_root != prev_state.previous_justified_root + else: + assert state.previous_justified_epoch == prev_state.previous_justified_epoch + assert state.previous_justified_root == prev_state.previous_justified_root + + if finalized_changed: + assert state.finalized_epoch > prev_state.finalized_epoch + assert state.finalized_root != prev_state.finalized_root + else: + assert state.finalized_epoch == prev_state.finalized_epoch + assert state.finalized_root == prev_state.finalized_root + + +def next_epoch_with_attestations(state, + fill_cur_epoch, + fill_prev_epoch): + post_state = deepcopy(state) + blocks = [] + for _ in range(spec.SLOTS_PER_EPOCH): + block = build_empty_block_for_next_slot(post_state) + if fill_cur_epoch: + slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 + if slot_to_attest >= get_epoch_start_slot(get_current_epoch(post_state)): + cur_attestation = get_valid_attestation(post_state, slot_to_attest) + fill_aggregate_attestation(post_state, cur_attestation) + block.body.attestations.append(cur_attestation) + + if fill_prev_epoch: + slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1 + prev_attestation = get_valid_attestation(post_state, slot_to_attest) + fill_aggregate_attestation(post_state, prev_attestation) + block.body.attestations.append(prev_attestation) + + state_transition(post_state, block) + blocks.append(block) + + return state, blocks, post_state + + +def test_finality_rule_4(state): + test_state = deepcopy(state) + + blocks = [] + for epoch in range(4): + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False) + blocks += new_blocks + + # justification/finalization skipped at GENESIS_EPOCH + if epoch == 0: + check_finality(test_state, prev_state, False, False, False) + # justification/finalization skipped at GENESIS_EPOCH + 1 + elif epoch == 1: + check_finality(test_state, prev_state, False, False, False) + elif epoch == 2: + check_finality(test_state, prev_state, True, False, False) + elif epoch >= 3: + # rule 4 of finality + check_finality(test_state, prev_state, True, True, True) + assert test_state.finalized_epoch == prev_state.current_justified_epoch + assert test_state.finalized_root == prev_state.current_justified_root + + return state, blocks, test_state + + +def test_finality_rule_1(state): + # get past first two epochs that finality does not run on + next_epoch(state) + next_epoch(state) + + pre_state = deepcopy(state) + test_state = deepcopy(state) + + blocks = [] + for epoch in range(3): + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True) + blocks += new_blocks + + if epoch == 0: + check_finality(test_state, prev_state, True, False, False) + elif epoch == 1: + check_finality(test_state, prev_state, True, True, False) + elif epoch == 2: + # finalized by rule 1 + check_finality(test_state, prev_state, True, True, True) + assert test_state.finalized_epoch == prev_state.previous_justified_epoch + assert test_state.finalized_root == prev_state.previous_justified_root + + return pre_state, blocks, test_state + + +def test_finality_rule_2(state): + # get past first two epochs that finality does not run on + next_epoch(state) + next_epoch(state) + + pre_state = deepcopy(state) + test_state = deepcopy(state) + + blocks = [] + for epoch in range(3): + if epoch == 0: + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False) + check_finality(test_state, prev_state, True, False, False) + elif epoch == 1: + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False) + check_finality(test_state, prev_state, False, True, False) + elif epoch == 2: + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True) + # finalized by rule 2 + check_finality(test_state, prev_state, True, False, True) + assert test_state.finalized_epoch == prev_state.previous_justified_epoch + assert test_state.finalized_root == prev_state.previous_justified_root + + blocks += new_blocks + + return pre_state, blocks, test_state + + +def test_finality_rule_3(state): + """ + Test scenario described here + https://github.com/ethereum/eth2.0-specs/issues/611#issuecomment-463612892 + """ + + # get past first two epochs that finality does not run on + next_epoch(state) + next_epoch(state) + + pre_state = deepcopy(state) + test_state = deepcopy(state) + + blocks = [] + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False) + blocks += new_blocks + check_finality(test_state, prev_state, True, False, False) + + # In epoch N, JE is set to N, prev JE is set to N-1 + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, False) + blocks += new_blocks + check_finality(test_state, prev_state, True, True, True) + + # In epoch N+1, JE is N, prev JE is N-1, and not enough messages get in to do anything + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, False) + blocks += new_blocks + check_finality(test_state, prev_state, False, True, False) + + # In epoch N+2, JE is N, prev JE is N, and enough messages from the previous epoch get in to justify N+1. + # N+1 now becomes the JE. Not enough messages from epoch N+2 itself get in to justify N+2 + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, False, True) + blocks += new_blocks + # rule 2 + check_finality(test_state, prev_state, True, False, True) + + # In epoch N+3, LJE is N+1, prev LJE is N, and enough messages get in to justify epochs N+2 and N+3. + prev_state, new_blocks, test_state = next_epoch_with_attestations(test_state, True, True) + blocks += new_blocks + # rule 3 + check_finality(test_state, prev_state, True, True, True) + assert test_state.finalized_epoch == prev_state.current_justified_epoch + assert test_state.finalized_root == prev_state.current_justified_root + + return pre_state, blocks, test_state diff --git a/test_libs/pyspec/tests/test_sanity.py b/test_libs/pyspec/tests/test_sanity.py index ba9dc6df6..b7d31f122 100644 --- a/test_libs/pyspec/tests/test_sanity.py +++ b/test_libs/pyspec/tests/test_sanity.py @@ -15,16 +15,13 @@ from eth2spec.phase0.spec import ( VoluntaryExit, # functions get_active_validator_indices, - get_balance, get_beacon_proposer_index, - get_block_root, + get_block_root_at_slot, + get_state_root, get_current_epoch, get_domain, - get_state_root, advance_slot, cache_state, - set_balance, - slot_to_epoch, verify_merkle_branch, hash, ) @@ -37,6 +34,7 @@ from eth2spec.utils.merkle_minimal import ( get_merkle_root, ) from .helpers import ( + get_balance, build_deposit_data, build_empty_block_for_next_slot, fill_aggregate_attestation, @@ -53,33 +51,6 @@ from .helpers import ( pytestmark = pytest.mark.sanity -def check_finality(state, - prev_state, - current_justified_changed, - previous_justified_changed, - finalized_changed): - if current_justified_changed: - assert state.current_justified_epoch > prev_state.current_justified_epoch - assert state.current_justified_root != prev_state.current_justified_root - else: - assert state.current_justified_epoch == prev_state.current_justified_epoch - assert state.current_justified_root == prev_state.current_justified_root - - if previous_justified_changed: - assert state.previous_justified_epoch > prev_state.previous_justified_epoch - assert state.previous_justified_root != prev_state.previous_justified_root - else: - assert state.previous_justified_epoch == prev_state.previous_justified_epoch - assert state.previous_justified_root == prev_state.previous_justified_root - - if finalized_changed: - assert state.finalized_epoch > prev_state.finalized_epoch - assert state.finalized_root != prev_state.finalized_root - else: - assert state.finalized_epoch == prev_state.finalized_epoch - assert state.finalized_root == prev_state.finalized_root - - def test_slot_transition(state): test_state = deepcopy(state) cache_state(test_state) @@ -96,7 +67,7 @@ def test_empty_block_transition(state): state_transition(test_state, block) assert len(test_state.eth1_data_votes) == len(state.eth1_data_votes) + 1 - assert get_block_root(test_state, state.slot) == block.previous_block_root + assert get_block_root_at_slot(test_state, state.slot) == block.previous_block_root return state, [block], test_state @@ -110,7 +81,7 @@ def test_skipped_slots(state): assert test_state.slot == block.slot for slot in range(state.slot, test_state.slot): - assert get_block_root(test_state, slot) == block.previous_block_root + assert get_block_root_at_slot(test_state, slot) == block.previous_block_root return state, [block], test_state @@ -124,7 +95,7 @@ def test_empty_epoch_transition(state): assert test_state.slot == block.slot for slot in range(state.slot, test_state.slot): - assert get_block_root(test_state, slot) == block.previous_block_root + assert get_block_root_at_slot(test_state, slot) == block.previous_block_root return state, [block], test_state @@ -144,33 +115,6 @@ def test_empty_epoch_transition_not_finalizing(state): return state, [block], test_state -def test_full_attestations_finalizing(state): - test_state = deepcopy(state) - - for slot in range(spec.MIN_ATTESTATION_INCLUSION_DELAY): - next_slot(test_state) - - for epoch in range(5): - for slot in range(spec.SLOTS_PER_EPOCH): - print(test_state.slot) - attestation = get_valid_attestation(test_state, test_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY) - fill_aggregate_attestation(test_state, attestation) - block = build_empty_block_for_next_slot(test_state) - block.body.attestations.append(attestation) - state_transition(test_state, block) - - if epoch == 0: - check_finality(test_state, state, False, False, False) - elif epoch == 1: - check_finality(test_state, state, False, False, False) - elif epoch == 2: - check_finality(test_state, state, True, False, False) - elif epoch == 3: - check_finality(test_state, state, True, True, False) - elif epoch == 4: - check_finality(test_state, state, True, True, True) - - def test_proposer_slashing(state): test_state = deepcopy(state) proposer_slashing = get_valid_proposer_slashing(state) @@ -233,9 +177,9 @@ def test_deposit_in_block(state): index = len(test_deposit_data_leaves) pubkey = pubkeys[index] privkey = privkeys[index] - deposit_data = build_deposit_data(pre_state, pubkey, privkey, spec.MAX_DEPOSIT_AMOUNT) + deposit_data = build_deposit_data(pre_state, pubkey, privkey, spec.MAX_EFFECTIVE_BALANCE) - item = hash(deposit_data.serialize()) + item = deposit_data.hash_tree_root() test_deposit_data_leaves.append(item) tree = calc_merkle_tree_from_leaves(tuple(test_deposit_data_leaves)) root = get_merkle_root((tuple(test_deposit_data_leaves))) @@ -257,7 +201,7 @@ def test_deposit_in_block(state): state_transition(post_state, block) assert len(post_state.validator_registry) == len(state.validator_registry) + 1 assert len(post_state.balances) == len(state.balances) + 1 - assert get_balance(post_state, index) == spec.MAX_DEPOSIT_AMOUNT + assert get_balance(post_state, index) == spec.MAX_EFFECTIVE_BALANCE assert post_state.validator_registry[index].pubkey == pubkeys[index] return pre_state, [block], post_state @@ -268,13 +212,13 @@ def test_deposit_top_up(state): test_deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry) validator_index = 0 - amount = spec.MAX_DEPOSIT_AMOUNT // 4 + amount = spec.MAX_EFFECTIVE_BALANCE // 4 pubkey = pubkeys[validator_index] privkey = privkeys[validator_index] deposit_data = build_deposit_data(pre_state, pubkey, privkey, amount) merkle_index = len(test_deposit_data_leaves) - item = hash(deposit_data.serialize()) + item = deposit_data.hash_tree_root() test_deposit_data_leaves.append(item) tree = calc_merkle_tree_from_leaves(tuple(test_deposit_data_leaves)) root = get_merkle_root((tuple(test_deposit_data_leaves))) @@ -303,6 +247,7 @@ def test_deposit_top_up(state): def test_attestation(state): + state.slot = spec.SLOTS_PER_EPOCH test_state = deepcopy(state) attestation = get_valid_attestation(state) @@ -316,8 +261,6 @@ def test_attestation(state): assert len(test_state.current_epoch_attestations) == len(state.current_epoch_attestations) + 1 - proposer_index = get_beacon_proposer_index(test_state) - assert test_state.balances[proposer_index] > state.balances[proposer_index] # # Epoch transition should move to previous_epoch_attestations @@ -381,6 +324,9 @@ def test_voluntary_exit(state): def test_transfer(state): + # overwrite default 0 to test + spec.MAX_TRANSFERS = 1 + pre_state = deepcopy(state) current_epoch = get_current_epoch(pre_state) sender_index = get_active_validator_indices(pre_state, current_epoch)[-1] @@ -411,7 +357,7 @@ def test_transfer(state): spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer_pubkey)[1:] ) # un-activate so validator can transfer - pre_state.validator_registry[sender_index].activation_epoch = spec.FAR_FUTURE_EPOCH + pre_state.validator_registry[sender_index].activation_eligibility_epoch = spec.FAR_FUTURE_EPOCH post_state = deepcopy(pre_state) # @@ -430,17 +376,15 @@ def test_transfer(state): def test_balance_driven_status_transitions(state): - pre_state = deepcopy(state) + current_epoch = get_current_epoch(state) + validator_index = get_active_validator_indices(state, current_epoch)[-1] - current_epoch = get_current_epoch(pre_state) - validator_index = get_active_validator_indices(pre_state, current_epoch)[-1] - - assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH # set validator balance to below ejection threshold - set_balance(pre_state, validator_index, spec.EJECTION_BALANCE - 1) + state.validator_registry[validator_index].effective_balance = spec.EJECTION_BALANCE - post_state = deepcopy(pre_state) + post_state = deepcopy(state) # # trigger epoch transition # @@ -450,14 +394,13 @@ def test_balance_driven_status_transitions(state): assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH - return pre_state, [block], post_state + return state, [block], post_state def test_historical_batch(state): - pre_state = deepcopy(state) - pre_state.slot += spec.SLOTS_PER_HISTORICAL_ROOT - (pre_state.slot % spec.SLOTS_PER_HISTORICAL_ROOT) - 1 + state.slot += spec.SLOTS_PER_HISTORICAL_ROOT - (state.slot % spec.SLOTS_PER_HISTORICAL_ROOT) - 1 - post_state = deepcopy(pre_state) + post_state = deepcopy(state) block = build_empty_block_for_next_slot(post_state) @@ -465,6 +408,30 @@ def test_historical_batch(state): assert post_state.slot == block.slot assert get_current_epoch(post_state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) == 0 - assert len(post_state.historical_roots) == len(pre_state.historical_roots) + 1 + assert len(post_state.historical_roots) == len(state.historical_roots) + 1 - return pre_state, [block], post_state + return state, [block], post_state + + +def test_eth1_data_votes(state): + post_state = deepcopy(state) + + expected_votes = 0 + assert len(state.eth1_data_votes) == expected_votes + + blocks = [] + for _ in range(spec.SLOTS_PER_ETH1_VOTING_PERIOD - 1): + block = build_empty_block_for_next_slot(post_state) + state_transition(post_state, block) + expected_votes += 1 + assert len(post_state.eth1_data_votes) == expected_votes + blocks.append(block) + + block = build_empty_block_for_next_slot(post_state) + state_transition(post_state, block) + blocks.append(block) + + assert post_state.slot % spec.SLOTS_PER_ETH1_VOTING_PERIOD == 0 + assert len(post_state.eth1_data_votes) == 1 + + return state, blocks, post_state diff --git a/test_libs/setup.py b/test_libs/setup.py new file mode 100644 index 000000000..b82fc369c --- /dev/null +++ b/test_libs/setup.py @@ -0,0 +1,29 @@ +from setuptools import setup, find_packages + + +deps = { + 'pyspec': [ + "eth-utils>=1.3.0,<2", + "eth-typing>=2.1.0,<3.0.0", + "pycryptodome==3.7.3", + "py_ecc>=1.6.0", + ], + 'test': [ + "pytest>=3.6,<3.7", + ], +} + +deps['dev'] = ( + deps['pyspec'] + + deps['test'] +) + +install_requires = deps['pyspec'] + + +setup( + name='pyspec', + packages=find_packages(exclude=["tests", "tests.*"]), + install_requires=install_requires, + extras_require=deps, +)