Merge branch 'dev-master-conflicts' into finality-testing
This commit is contained in:
commit
23c9b8b2d1
|
@ -1,89 +1,97 @@
|
|||
version: 2.1
|
||||
commands:
|
||||
restore_cached_venv:
|
||||
description: "Restores a cached venv"
|
||||
parameters:
|
||||
reqs_checksum:
|
||||
type: string
|
||||
default: "1234"
|
||||
venv_name:
|
||||
type: string
|
||||
default: "default-name"
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- << parameters.venv_name >>-venv-<< parameters.reqs_checksum >>
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- << parameters.venv_name >>-venv-
|
||||
save_cached_venv:
|
||||
description: "Saves a venv into a cache"
|
||||
parameters:
|
||||
reqs_checksum:
|
||||
type: string
|
||||
default: "1234"
|
||||
venv_path:
|
||||
type: string
|
||||
default: "venv"
|
||||
venv_name:
|
||||
type: string
|
||||
default: "default-name"
|
||||
steps:
|
||||
- save_cache:
|
||||
key: << parameters.venv_name >>-venv-<< parameters.reqs_checksum >>
|
||||
paths: << parameters.venv_path >>
|
||||
jobs:
|
||||
build:
|
||||
checkout_specs:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/repo
|
||||
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
# Restore git repo at point close to target branch/revision, to speed up checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- v1-specs-repo-{{ .Branch }}-
|
||||
- v1-specs-repo-
|
||||
- checkout
|
||||
- run:
|
||||
name: Build pyspec
|
||||
command: make pyspec
|
||||
|
||||
name: Clean up git repo to reduce cache size
|
||||
command: git gc
|
||||
# Save the git checkout as a cache, to make cloning next time faster.
|
||||
- save_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- ~/specs-repo
|
||||
install_test:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v1-pyspec
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}'
|
||||
- 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
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
working_directory: ~/specs-repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cached_venv:
|
||||
venv_name: v1-pyspec
|
||||
reqs_checksum: '{{ checksum "test_libs/pyspec/requirements.txt" }}'
|
||||
- run:
|
||||
name: Run py-tests
|
||||
command: make test
|
||||
|
||||
# TODO see #928: decide on CI triggering of yaml tests building,
|
||||
# and destination of output (new yaml tests LFS-configured repository)
|
||||
#
|
||||
# - run:
|
||||
# name: Generate YAML tests
|
||||
# command: make gen_yaml_tests
|
||||
#
|
||||
# - store_artifacts:
|
||||
# path: test-reports
|
||||
# destination: test-reports
|
||||
#
|
||||
# - run:
|
||||
# name: Save YAML tests for deployment
|
||||
# command: |
|
||||
# mkdir /tmp/workspace
|
||||
# cp -r yaml_tests /tmp/workspace/
|
||||
# git log -1 >> /tmp/workspace/latest_commit_message
|
||||
# - persist_to_workspace:
|
||||
# root: /tmp/workspace
|
||||
# paths:
|
||||
# - yaml_tests
|
||||
# - latest_commit_message
|
||||
# commit:
|
||||
# docker:
|
||||
# - image: circleci/python:3.6
|
||||
# steps:
|
||||
# - attach_workspace:
|
||||
# at: /tmp/workspace
|
||||
# - add_ssh_keys:
|
||||
# fingerprints:
|
||||
# - "01:85:b6:36:96:a6:84:72:e4:9b:4e:38:ee:21:97:fa"
|
||||
# - run:
|
||||
# name: Checkout test repository
|
||||
# command: |
|
||||
# ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
||||
# git clone git@github.com:ethereum/eth2.0-tests.git
|
||||
# - run:
|
||||
# name: Commit and push generated YAML tests
|
||||
# command: |
|
||||
# cd eth2.0-tests
|
||||
# git config user.name 'eth2TestGenBot'
|
||||
# git config user.email '47188154+eth2TestGenBot@users.noreply.github.com'
|
||||
# for filename in /tmp/workspace/yaml_tests/*; do
|
||||
# rm -rf $(basename $filename)
|
||||
# cp -r $filename .
|
||||
# done
|
||||
# git add .
|
||||
# if git diff --cached --exit-code >& /dev/null; then
|
||||
# echo "No changes to commit"
|
||||
# else
|
||||
# echo -e "Update generated tests\n\nLatest commit message from eth2.0-specs:\n" > commit_message
|
||||
# cat /tmp/workspace/latest_commit_message >> commit_message
|
||||
# git commit -F commit_message
|
||||
# git push origin master
|
||||
# fi
|
||||
#workflows:
|
||||
# version: 2.1
|
||||
#
|
||||
# build_and_commit:
|
||||
# jobs:
|
||||
# - build:
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# - commit:
|
||||
# requires:
|
||||
# - build
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# branches:
|
||||
# ignore: /.*/
|
||||
command: make citest
|
||||
- store_test_results:
|
||||
path: test_libs/pyspec/test-reports
|
||||
workflows:
|
||||
version: 2.1
|
||||
test_spec:
|
||||
jobs:
|
||||
- checkout_specs
|
||||
- install_test:
|
||||
requires:
|
||||
- checkout_specs
|
||||
- test:
|
||||
requires:
|
||||
- install_test
|
||||
|
|
|
@ -8,7 +8,7 @@ venv
|
|||
build/
|
||||
output/
|
||||
|
||||
yaml_tests/
|
||||
eth2.0-spec-tests/
|
||||
.pytest_cache
|
||||
|
||||
# Dynamically built from Markdown spec
|
||||
|
|
48
Makefile
48
Makefile
|
@ -2,7 +2,7 @@ SPEC_DIR = ./specs
|
|||
SCRIPT_DIR = ./scripts
|
||||
TEST_LIBS_DIR = ./test_libs
|
||||
PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec
|
||||
YAML_TEST_DIR = ./yaml_tests
|
||||
YAML_TEST_DIR = ./eth2.0-spec-tests/tests
|
||||
GENERATOR_DIR = ./test_generators
|
||||
CONFIGS_DIR = ./configs
|
||||
|
||||
|
@ -16,7 +16,7 @@ PY_SPEC_PHASE_0_TARGETS = $(PY_SPEC_DIR)/eth2spec/phase0/spec.py
|
|||
PY_SPEC_ALL_TARGETS = $(PY_SPEC_PHASE_0_TARGETS)
|
||||
|
||||
|
||||
.PHONY: clean all test gen_yaml_tests pyspec phase0
|
||||
.PHONY: clean all test citest gen_yaml_tests pyspec phase0 install_test
|
||||
|
||||
all: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR) $(YAML_TEST_TARGETS)
|
||||
|
||||
|
@ -27,11 +27,17 @@ clean:
|
|||
rm -rf $(PY_SPEC_ALL_TARGETS)
|
||||
|
||||
# "make gen_yaml_tests" to run generators
|
||||
gen_yaml_tests: $(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;
|
||||
|
||||
# runs a limited set of tests against a minimal config
|
||||
test: $(PY_SPEC_ALL_TARGETS)
|
||||
cd $(PY_SPEC_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt; python -m pytest -m minimal_config .
|
||||
cd $(PY_SPEC_DIR); . venv/bin/activate; python -m pytest -m minimal_config .
|
||||
|
||||
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 .
|
||||
|
||||
# "make pyspec" to create the pyspec for all phases.
|
||||
pyspec: $(PY_SPEC_ALL_TARGETS)
|
||||
|
@ -48,26 +54,32 @@ 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 <filepath>" 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 <filepath>" 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)
|
||||
$(YAML_TEST_DIR)%: $(YAML_TEST_DIR)
|
||||
$(YAML_TEST_DIR)%: $(PY_SPEC_ALL_TARGETS) $(YAML_TEST_DIR)
|
||||
$(call build_yaml_tests,$*)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -42,8 +42,8 @@ HIGH_BALANCE_INCREMENT: 1000000000
|
|||
# Initial values
|
||||
# ---------------------------------------------------------------
|
||||
GENESIS_FORK_VERSION: 0x00000000
|
||||
# 2**32, GENESIS_EPOCH is derived from this constant
|
||||
GENESIS_SLOT: 4294967296
|
||||
# 0, GENESIS_EPOCH is derived from this constant
|
||||
GENESIS_SLOT: 0
|
||||
GENESIS_START_SHARD: 0
|
||||
# 2**64 - 1
|
||||
FAR_FUTURE_EPOCH: 18446744073709551615
|
||||
|
@ -116,7 +116,7 @@ MAX_TRANSFERS: 16
|
|||
|
||||
# Signature domains
|
||||
# ---------------------------------------------------------------
|
||||
DOMAIN_BEACON_BLOCK: 0
|
||||
DOMAIN_BEACON_PROPOSER: 0
|
||||
DOMAIN_RANDAO: 1
|
||||
DOMAIN_ATTESTATION: 2
|
||||
DOMAIN_DEPOSIT: 3
|
||||
|
|
|
@ -42,8 +42,8 @@ HIGH_BALANCE_INCREMENT: 1000000000
|
|||
# Initial values
|
||||
# ---------------------------------------------------------------
|
||||
GENESIS_FORK_VERSION: 0x00000000
|
||||
# 2**32, GENESIS_EPOCH is derived from this constant
|
||||
GENESIS_SLOT: 4294967296
|
||||
# 0, GENESIS_EPOCH is derived from this constant
|
||||
GENESIS_SLOT: 0
|
||||
GENESIS_START_SHARD: 0
|
||||
# 2**64 - 1
|
||||
FAR_FUTURE_EPOCH: 18446744073709551615
|
||||
|
@ -116,7 +116,7 @@ MAX_TRANSFERS: 16
|
|||
|
||||
# Signature domains
|
||||
# ---------------------------------------------------------------
|
||||
DOMAIN_BEACON_BLOCK: 0
|
||||
DOMAIN_BEACON_PROPOSER: 0
|
||||
DOMAIN_RANDAO: 1
|
||||
DOMAIN_ATTESTATION: 2
|
||||
DOMAIN_DEPOSIT: 3
|
||||
|
|
|
@ -62,4 +62,9 @@ def get_spec(file_name: str) -> List[str]:
|
|||
code_lines.append('')
|
||||
for type_line in ssz_type:
|
||||
code_lines.append(' ' + type_line)
|
||||
code_lines.append('')
|
||||
code_lines.append('ssz_types = [' + ', '.join([f'\'{ssz_type_name}\'' for (ssz_type_name, _) in type_defs]) + ']')
|
||||
code_lines.append('')
|
||||
code_lines.append('def get_ssz_type_by_name(name: str) -> SSZType: return globals()[name]')
|
||||
code_lines.append('')
|
||||
return code_lines
|
||||
|
|
|
@ -86,9 +86,9 @@ 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` (ie. integers `mod q`) and converts it to a regular integer.
|
||||
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.
|
||||
|
||||
```python
|
||||
Fq2_order = q ** 2 - 1
|
||||
|
|
|
@ -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. It reflects recent spec changes and takes precedence over the Python proof-of-concept implementation [[python-poc]](https://github.com/ethereum/beacon_chain).
|
||||
|
||||
## Table of contents
|
||||
<!-- TOC -->
|
||||
|
@ -51,7 +51,6 @@
|
|||
- [`hash`](#hash)
|
||||
- [`hash_tree_root`](#hash_tree_root)
|
||||
- [`signing_root`](#signing_root)
|
||||
- [`get_temporary_block_header`](#get_temporary_block_header)
|
||||
- [`slot_to_epoch`](#slot_to_epoch)
|
||||
- [`get_previous_epoch`](#get_previous_epoch)
|
||||
- [`get_current_epoch`](#get_current_epoch)
|
||||
|
@ -81,7 +80,6 @@
|
|||
- [`bytes_to_int`](#bytes_to_int)
|
||||
- [`get_effective_balance`](#get_effective_balance)
|
||||
- [`get_total_balance`](#get_total_balance)
|
||||
- [`get_fork_version`](#get_fork_version)
|
||||
- [`get_domain`](#get_domain)
|
||||
- [`get_bitfield_bit`](#get_bitfield_bit)
|
||||
- [`verify_bitfield`](#verify_bitfield)
|
||||
|
@ -96,7 +94,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)
|
||||
- [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract)
|
||||
|
@ -194,7 +191,7 @@ 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 |
|
||||
|
||||
|
@ -202,13 +199,10 @@ These configurations are updated for releases, but may be out of sync during `de
|
|||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `GENESIS_FORK_VERSION` | `int_to_bytes4(0)` |
|
||||
| `GENESIS_SLOT` | `0` |
|
||||
| `GENESIS_EPOCH` | `0` |
|
||||
| `GENESIS_START_SHARD` | `0` |
|
||||
| `FAR_FUTURE_EPOCH` | `2**64 - 1` |
|
||||
| `ZERO_HASH` | `int_to_bytes32(0)` |
|
||||
| `EMPTY_SIGNATURE` | `int_to_bytes96(0)` |
|
||||
| `BLS_WITHDRAWAL_PREFIX_BYTE` | `int_to_bytes1(0)` |
|
||||
|
||||
### Time parameters
|
||||
|
@ -246,7 +240,7 @@ These configurations are updated for releases, but may be out of sync during `de
|
|||
| `INACTIVITY_PENALTY_QUOTIENT` | `2**24` (= 16,777,216) |
|
||||
| `MIN_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
|
||||
|
@ -258,13 +252,13 @@ 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
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_BEACON_BLOCK` | `0` |
|
||||
| `DOMAIN_BEACON_PROPOSER` | `0` |
|
||||
| `DOMAIN_RANDAO` | `1` |
|
||||
| `DOMAIN_ATTESTATION` | `2` |
|
||||
| `DOMAIN_DEPOSIT` | `3` |
|
||||
|
@ -300,7 +294,7 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
'epoch': 'uint64',
|
||||
# Root of the previous crosslink
|
||||
'previous_crosslink_root': 'bytes32',
|
||||
# Shard data since the previous crosslink
|
||||
# Root of the crosslinked shard data since the previous crosslink
|
||||
'crosslink_data_root': 'bytes32',
|
||||
}
|
||||
```
|
||||
|
@ -359,7 +353,7 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
# Attestation data
|
||||
'data': AttestationData,
|
||||
# Aggregate signature
|
||||
'aggregate_signature': 'bytes96',
|
||||
'signature': 'bytes96',
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -374,7 +368,7 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
# Amount in Gwei
|
||||
'amount': 'uint64',
|
||||
# Container self-signature
|
||||
'proof_of_possession': 'bytes96',
|
||||
'signature': 'bytes96',
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -473,7 +467,7 @@ The types are defined topologically to aid in facilitating an executable version
|
|||
# Custody bitfield
|
||||
'custody_bitfield': 'bytes',
|
||||
# BLS aggregate signature
|
||||
'aggregate_signature': 'bytes96',
|
||||
'signature': 'bytes96',
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -641,23 +635,6 @@ Note: We aim to migrate to a S[T/N]ARK-friendly hash function in a future Ethere
|
|||
|
||||
`def signing_root(object: SSZContainer) -> Bytes32` is a function defined in the [SimpleSerialize spec](../simple-serialize.md#self-signed-containers) to compute signing messages.
|
||||
|
||||
### `get_temporary_block_header`
|
||||
|
||||
```python
|
||||
def get_temporary_block_header(block: BeaconBlock) -> BeaconBlockHeader:
|
||||
"""
|
||||
Return the block header corresponding to a block with ``state_root`` set to ``ZERO_HASH``.
|
||||
"""
|
||||
return BeaconBlockHeader(
|
||||
slot=block.slot,
|
||||
previous_block_root=block.previous_block_root,
|
||||
state_root=ZERO_HASH,
|
||||
block_body_root=hash_tree_root(block.body),
|
||||
# signing_root(block) is used for block id purposes so signature is a stub
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
```
|
||||
|
||||
### `slot_to_epoch`
|
||||
|
||||
```python
|
||||
|
@ -820,7 +797,7 @@ 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)
|
||||
return max(
|
||||
|
@ -836,6 +813,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)
|
||||
```
|
||||
|
||||
|
@ -912,7 +892,7 @@ def get_block_root(state: BeaconState,
|
|||
return state.latest_block_roots[slot % SLOTS_PER_HISTORICAL_ROOT]
|
||||
```
|
||||
|
||||
`get_block_root(_, s)` should always return `signed_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(_, 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_state_root`
|
||||
|
||||
|
@ -978,7 +958,7 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex:
|
|||
while True:
|
||||
candidate = 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:
|
||||
if get_effective_balance(state, candidate) * 256 > MAX_EFFECTIVE_BALANCE * random_byte:
|
||||
return candidate
|
||||
i += 1
|
||||
```
|
||||
|
@ -1033,7 +1013,7 @@ 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)
|
||||
return min(get_balance(state, index), MAX_EFFECTIVE_BALANCE)
|
||||
```
|
||||
|
||||
### `get_total_balance`
|
||||
|
@ -1046,30 +1026,18 @@ def get_total_balance(state: BeaconState, validators: List[ValidatorIndex]) -> G
|
|||
return sum([get_effective_balance(state, i) for i in validators])
|
||||
```
|
||||
|
||||
### `get_fork_version`
|
||||
|
||||
```python
|
||||
def get_fork_version(fork: Fork,
|
||||
epoch: Epoch) -> bytes:
|
||||
"""
|
||||
Return the fork version of the given ``epoch``.
|
||||
"""
|
||||
if epoch < fork.epoch:
|
||||
return fork.previous_version
|
||||
else:
|
||||
return fork.current_version
|
||||
```
|
||||
|
||||
### `get_domain`
|
||||
|
||||
```python
|
||||
def get_domain(fork: Fork,
|
||||
epoch: Epoch,
|
||||
domain_type: int) -> int:
|
||||
def get_domain(state: BeaconState,
|
||||
domain_type: int,
|
||||
message_epoch: int=None) -> int:
|
||||
"""
|
||||
Get the domain number that represents the fork meta and signature domain.
|
||||
Return the signature domain (fork version concatenated with domain type) of a message.
|
||||
"""
|
||||
return bytes_to_int(get_fork_version(fork, epoch) + int_to_bytes4(domain_type))
|
||||
epoch = get_current_epoch(state) if message_epoch is None else message_epoch
|
||||
fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
|
||||
return bytes_to_int(fork_version + int_to_bytes4(domain_type))
|
||||
```
|
||||
|
||||
### `get_bitfield_bit`
|
||||
|
@ -1115,7 +1083,7 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation) -> IndexedA
|
|||
custody_bit_0_indices=custody_bit_0_indices,
|
||||
custody_bit_1_indices=custody_bit_1_indices,
|
||||
data=attestation.data,
|
||||
aggregate_signature=attestation.aggregate_signature,
|
||||
signature=attestation.signature,
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -1153,8 +1121,8 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA
|
|||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=indexed_attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
signature=indexed_attestation.aggregate_signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(indexed_attestation.data.slot), DOMAIN_ATTESTATION),
|
||||
signature=indexed_attestation.signature,
|
||||
domain=get_domain(state, DOMAIN_ATTESTATION, slot_to_epoch(indexed_attestation.data.slot)),
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -1239,22 +1207,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
|
||||
|
@ -1270,7 +1222,7 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
|
|||
|
||||
# Compute exit queue epoch
|
||||
exit_epochs = [v.exit_epoch for v in state.validator_registry if v.exit_epoch != FAR_FUTURE_EPOCH]
|
||||
exit_queue_epoch = sorted(exit_epochs + [get_delayed_activation_exit_epoch(get_current_epoch(state))])[-1]
|
||||
exit_queue_epoch = max(exit_epochs + [get_delayed_activation_exit_epoch(get_current_epoch(state))])
|
||||
exit_queue_churn = len([v for v in state.validator_registry if v.exit_epoch == exit_queue_epoch])
|
||||
if exit_queue_churn >= get_churn_limit(state):
|
||||
exit_queue_epoch += 1
|
||||
|
@ -1310,7 +1262,7 @@ The initial deployment phases of Ethereum 2.0 are implemented without consensus
|
|||
|
||||
### Deposit arguments
|
||||
|
||||
The deposit contract has a single `deposit` function which takes as argument a SimpleSerialize'd `DepositData`.
|
||||
The deposit contract has a single `deposit` function which takes as argument the `DepositData` elements.
|
||||
|
||||
### Withdrawal credentials
|
||||
|
||||
|
@ -1323,7 +1275,7 @@ The private key corresponding to `withdrawal_pubkey` will be required to initiat
|
|||
|
||||
### `Deposit` logs
|
||||
|
||||
Every Ethereum 1.0 deposit, of size between `MIN_DEPOSIT_AMOUNT` and `MAX_DEPOSIT_AMOUNT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12 signature) is not verified by the deposit contract.
|
||||
Every Ethereum 1.0 deposit, of size at least `MIN_DEPOSIT_AMOUNT`, emits a `Deposit` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.
|
||||
|
||||
### `Eth2Genesis` log
|
||||
|
||||
|
@ -1345,7 +1297,7 @@ For convenience, we provide the interface to the contract here:
|
|||
|
||||
* `__init__()`: initializes the contract
|
||||
* `get_deposit_root() -> bytes32`: returns the current root of the deposit tree
|
||||
* `deposit(bytes[512])`: adds a deposit instance to the deposit tree, incorporating the input argument and the value transferred in the given call. Note: the amount of value transferred *must* be within `MIN_DEPOSIT_AMOUNT` and `MAX_DEPOSIT_AMOUNT`, inclusive. Each of these constants are specified in units of Gwei.
|
||||
* `deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96])`: adds a deposit instance to the deposit tree, incorporating the input arguments and the value transferred in the given call. Note: the amount of value transferred *must* be at least `MIN_DEPOSIT_AMOUNT`. Each of these constants are specified in units of Gwei.
|
||||
|
||||
## On genesis
|
||||
|
||||
|
@ -1358,35 +1310,7 @@ When enough full deposits have been made to the deposit contract, an `Eth2Genesi
|
|||
* `genesis_eth1_data.deposit_count` is the `deposit_count` contained in the `Eth2Genesis` log.
|
||||
* `genesis_eth1_data.block_hash` is the hash of the Ethereum 1.0 block that emitted the `Eth2Genesis` log.
|
||||
* Let `genesis_state = get_genesis_beacon_state(genesis_validator_deposits, genesis_time, genesis_eth1_data)`.
|
||||
* Let `genesis_block = get_empty_block()`.
|
||||
* Set `genesis_block.state_root = hash_tree_root(genesis_state)`.
|
||||
|
||||
```python
|
||||
def get_empty_block() -> BeaconBlock:
|
||||
"""
|
||||
Get an empty ``BeaconBlock``.
|
||||
"""
|
||||
return BeaconBlock(
|
||||
slot=GENESIS_SLOT,
|
||||
previous_block_root=ZERO_HASH,
|
||||
state_root=ZERO_HASH,
|
||||
body=BeaconBlockBody(
|
||||
randao_reveal=EMPTY_SIGNATURE,
|
||||
eth1_data=Eth1Data(
|
||||
deposit_root=ZERO_HASH,
|
||||
deposit_count=0,
|
||||
block_hash=ZERO_HASH,
|
||||
),
|
||||
proposer_slashings=[],
|
||||
attester_slashings=[],
|
||||
attestations=[],
|
||||
deposits=[],
|
||||
voluntary_exits=[],
|
||||
transfers=[],
|
||||
),
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
```
|
||||
* Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
|
||||
|
||||
```python
|
||||
def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],
|
||||
|
@ -1395,59 +1319,17 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],
|
|||
"""
|
||||
Get the genesis ``BeaconState``.
|
||||
"""
|
||||
state = BeaconState(
|
||||
# Misc
|
||||
slot=GENESIS_SLOT,
|
||||
genesis_time=genesis_time,
|
||||
fork=Fork(
|
||||
previous_version=GENESIS_FORK_VERSION,
|
||||
current_version=GENESIS_FORK_VERSION,
|
||||
epoch=GENESIS_EPOCH,
|
||||
),
|
||||
|
||||
# Validator registry
|
||||
validator_registry=[],
|
||||
balances=[],
|
||||
|
||||
# Randomness and committees
|
||||
latest_randao_mixes=Vector([ZERO_HASH for _ in range(LATEST_RANDAO_MIXES_LENGTH)]),
|
||||
latest_start_shard=GENESIS_START_SHARD,
|
||||
|
||||
# Finality
|
||||
previous_epoch_attestations=[],
|
||||
current_epoch_attestations=[],
|
||||
previous_justified_epoch=GENESIS_EPOCH,
|
||||
current_justified_epoch=GENESIS_EPOCH,
|
||||
previous_justified_root=ZERO_HASH,
|
||||
current_justified_root=ZERO_HASH,
|
||||
justification_bitfield=0,
|
||||
finalized_epoch=GENESIS_EPOCH,
|
||||
finalized_root=ZERO_HASH,
|
||||
|
||||
# Recent state
|
||||
current_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
|
||||
previous_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
|
||||
latest_block_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
|
||||
latest_state_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
|
||||
latest_active_index_roots=Vector([ZERO_HASH for _ in range(LATEST_ACTIVE_INDEX_ROOTS_LENGTH)]),
|
||||
latest_slashed_balances=Vector([0 for _ in range(LATEST_SLASHED_EXIT_LENGTH)]),
|
||||
latest_block_header=get_temporary_block_header(get_empty_block()),
|
||||
historical_roots=[],
|
||||
|
||||
# Ethereum 1.0 chain data
|
||||
latest_eth1_data=genesis_eth1_data,
|
||||
eth1_data_votes=[],
|
||||
deposit_index=0,
|
||||
)
|
||||
state = BeaconState(genesis_time=genesis_time, latest_eth1_data=genesis_eth1_data)
|
||||
|
||||
# Process genesis deposits
|
||||
for deposit in genesis_validator_deposits:
|
||||
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 get_effective_balance(state, index) >= 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):
|
||||
|
@ -1852,20 +1734,22 @@ 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 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:
|
||||
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
|
||||
|
@ -1950,17 +1834,16 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
|||
# Verify that the parent matches
|
||||
assert block.previous_block_root == signing_root(state.latest_block_header)
|
||||
# Save current block as the new latest block
|
||||
state.latest_block_header = get_temporary_block_header(block)
|
||||
state.latest_block_header = BeaconBlockHeader(
|
||||
slot=block.slot,
|
||||
previous_block_root=block.previous_block_root,
|
||||
block_body_root=hash_tree_root(block.body),
|
||||
)
|
||||
# Verify proposer is not slashed
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state)]
|
||||
assert not proposer.slashed
|
||||
# Verify proposer signature
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=signing_root(block),
|
||||
signature=block.signature,
|
||||
domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK)
|
||||
)
|
||||
assert bls_verify(proposer.pubkey, signing_root(block), block.signature, get_domain(state, DOMAIN_BEACON_PROPOSER))
|
||||
```
|
||||
|
||||
#### RANDAO
|
||||
|
@ -1969,12 +1852,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
|
|||
def process_randao(state: BeaconState, block: BeaconBlock) -> None:
|
||||
proposer = state.validator_registry[get_beacon_proposer_index(state)]
|
||||
# Verify that the provided randao value is valid
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=hash_tree_root(get_current_epoch(state)),
|
||||
signature=block.body.randao_reveal,
|
||||
domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)
|
||||
)
|
||||
assert bls_verify(proposer.pubkey, hash_tree_root(get_current_epoch(state)), block.body.randao_reveal, get_domain(state, DOMAIN_RANDAO))
|
||||
# Mix it in
|
||||
state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = (
|
||||
xor(get_randao_mix(state, get_current_epoch(state)),
|
||||
|
@ -2015,12 +1893,9 @@ def process_proposer_slashing(state: BeaconState,
|
|||
assert is_slashable_validator(proposer, get_current_epoch(state))
|
||||
# Signatures are valid
|
||||
for header in (proposer_slashing.header_1, proposer_slashing.header_2):
|
||||
assert bls_verify(
|
||||
pubkey=proposer.pubkey,
|
||||
message_hash=signing_root(header),
|
||||
signature=header.signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(header.slot), DOMAIN_BEACON_BLOCK)
|
||||
)
|
||||
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, slot_to_epoch(header.slot))
|
||||
assert bls_verify(proposer.pubkey, signing_root(header), header.signature, domain)
|
||||
|
||||
slash_validator(state, proposer_slashing.proposer_index)
|
||||
```
|
||||
|
||||
|
@ -2135,7 +2010,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
|
||||
# Verify the Merkle branch
|
||||
merkle_branch_is_valid = verify_merkle_branch(
|
||||
leaf=hash(serialize(deposit.data)), # 48 + 32 + 8 + 96 = 184 bytes serialization
|
||||
leaf=hash_tree_root(deposit.data),
|
||||
proof=deposit.proof,
|
||||
depth=DEPOSIT_CONTRACT_TREE_DEPTH,
|
||||
index=deposit.index,
|
||||
|
@ -2154,18 +2029,8 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
amount = deposit.data.amount
|
||||
|
||||
if pubkey not in validator_pubkeys:
|
||||
# Verify the proof of possession
|
||||
proof_is_valid = bls_verify(
|
||||
pubkey=pubkey,
|
||||
message_hash=signing_root(deposit.data),
|
||||
signature=deposit.data.proof_of_possession,
|
||||
domain=get_domain(
|
||||
state.fork,
|
||||
get_current_epoch(state),
|
||||
DOMAIN_DEPOSIT,
|
||||
)
|
||||
)
|
||||
if not proof_is_valid:
|
||||
# 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
|
||||
|
@ -2176,8 +2041,6 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
|
|||
activation_epoch=FAR_FUTURE_EPOCH,
|
||||
exit_epoch=FAR_FUTURE_EPOCH,
|
||||
withdrawable_epoch=FAR_FUTURE_EPOCH,
|
||||
slashed=False,
|
||||
high_balance=0
|
||||
)
|
||||
|
||||
# Note: In phase 2 registry indices that have been withdrawn for a long time will be recycled.
|
||||
|
@ -2212,20 +2075,14 @@ def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None:
|
|||
# Verify the validator has been active long enough
|
||||
assert get_current_epoch(state) - validator.activation_epoch >= PERSISTENT_COMMITTEE_PERIOD
|
||||
# Verify signature
|
||||
assert bls_verify(
|
||||
pubkey=validator.pubkey,
|
||||
message_hash=signing_root(exit),
|
||||
signature=exit.signature,
|
||||
domain=get_domain(state.fork, exit.epoch, DOMAIN_VOLUNTARY_EXIT)
|
||||
)
|
||||
domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, exit.epoch)
|
||||
assert bls_verify(validator.pubkey, signing_root(exit), exit.signature, domain)
|
||||
# Initiate exit
|
||||
initiate_validator_exit(state, exit.validator_index)
|
||||
```
|
||||
|
||||
##### 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:
|
||||
|
@ -2240,10 +2097,11 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
|||
assert get_balance(state, 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 <= get_balance(state, transfer.sender)
|
||||
)
|
||||
# Verify that the pubkey is valid
|
||||
assert (
|
||||
|
@ -2251,12 +2109,7 @@ def process_transfer(state: BeaconState, transfer: Transfer) -> None:
|
|||
BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:]
|
||||
)
|
||||
# Verify that the signature is valid
|
||||
assert bls_verify(
|
||||
pubkey=transfer.pubkey,
|
||||
message_hash=signing_root(transfer),
|
||||
signature=transfer.signature,
|
||||
domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER)
|
||||
)
|
||||
assert bls_verify(transfer.pubkey, signing_root(transfer), transfer.signature, get_domain(state, DOMAIN_TRANSFER))
|
||||
# Process the transfer
|
||||
decrease_balance(state, transfer.sender, transfer.amount + transfer.fee)
|
||||
increase_balance(state, transfer.recipient, transfer.amount)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -102,7 +102,7 @@ def get_generalized_indices(obj: Any, path: List[int], root: int=1) -> List[int]
|
|||
|
||||
## Merkle multiproofs
|
||||
|
||||
We define a Merkle multiproof as a minimal subset of nodes in a Merkle tree needed to fully authenticate that a set of nodes actually are part of a Merkle tree with some specified root, at a particular set of generalized indices. For example, here is the Merkle multiproof for positions 0, 1, 6 in an 8-node Merkle tree (ie. generalized indices 8, 9, 14):
|
||||
We define a Merkle multiproof as a minimal subset of nodes in a Merkle tree needed to fully authenticate that a set of nodes actually are part of a Merkle tree with some specified root, at a particular set of generalized indices. For example, here is the Merkle multiproof for positions 0, 1, 6 in an 8-node Merkle tree (i.e. generalized indices 8, 9, 14):
|
||||
|
||||
```
|
||||
.
|
||||
|
|
|
@ -27,7 +27,7 @@ __NOTICE__: This document is a work-in-progress for researchers and implementers
|
|||
|
||||
### Expansions
|
||||
|
||||
We define an "expansion" of an object as an object where a field in an object that is meant to represent the `hash_tree_root` of another object is replaced by the object. Note that defining expansions is not a consensus-layer-change; it is merely a "re-interpretation" of the object. Particularly, the `hash_tree_root` of an expansion of an object is identical to that of the original object, and we can define expansions where, given a complete history, it is always possible to compute the expansion of any object in the history. The opposite of an expansion is a "summary" (eg. `BeaconBlockHeader` is a summary of `BeaconBlock`).
|
||||
We define an "expansion" of an object as an object where a field in an object that is meant to represent the `hash_tree_root` of another object is replaced by the object. Note that defining expansions is not a consensus-layer-change; it is merely a "re-interpretation" of the object. Particularly, the `hash_tree_root` of an expansion of an object is identical to that of the original object, and we can define expansions where, given a complete history, it is always possible to compute the expansion of any object in the history. The opposite of an expansion is a "summary" (e.g. `BeaconBlockHeader` is a summary of `BeaconBlock`).
|
||||
|
||||
We define two expansions:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -9,6 +9,7 @@ This is a **work in progress** describing typing, serialization and Merkleizatio
|
|||
- [Basic types](#basic-types)
|
||||
- [Composite types](#composite-types)
|
||||
- [Aliases](#aliases)
|
||||
- [Default values](#default-values)
|
||||
- [Serialization](#serialization)
|
||||
- [`"uintN"`](#uintn)
|
||||
- [`"bool"`](#bool)
|
||||
|
@ -33,11 +34,11 @@ This is a **work in progress** describing typing, serialization and Merkleizatio
|
|||
|
||||
### Composite types
|
||||
|
||||
* **container**: ordered heterogenous collection of values
|
||||
* **container**: ordered heterogeneous collection of values
|
||||
* key-pair curly bracket notation `{}`, e.g. `{"foo": "uint64", "bar": "bool"}`
|
||||
* **vector**: ordered fixed-length homogeneous collection of values
|
||||
* angle bracket notation `[type, N]`, e.g. `["uint64", N]`
|
||||
* **list**: ordered variable-length homogenous collection of values
|
||||
* **list**: ordered variable-length homogeneous collection of values
|
||||
* angle bracket notation `[type]`, e.g. `["uint64"]`
|
||||
|
||||
We recursively define "variable-size" types to be lists and all types that contains a variable-size type. All other types are said to be "fixed-size".
|
||||
|
@ -50,6 +51,10 @@ For convenience we alias:
|
|||
* `"bytes"` to `["byte"]` (this is *not* a basic type)
|
||||
* `"bytesN"` to `["byte", N]` (this is *not* a basic type)
|
||||
|
||||
### Default values
|
||||
|
||||
The default value of a type upon initialization is recursively defined using `0` for `"uintN"`, `False` for `"bool"`, and `[]` for lists.
|
||||
|
||||
## Serialization
|
||||
|
||||
We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `"bytes"`.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -175,3 +175,24 @@ To prevent parsing of hundreds of different YAML files to test a specific test t
|
|||
│ ... <--- more handlers
|
||||
... <--- more test types
|
||||
```
|
||||
|
||||
|
||||
## Note for implementers
|
||||
|
||||
The basic pattern for test-suite loading and running is:
|
||||
|
||||
Iterate suites for given test-type, or sub-type (e.g. `operations > deposits`):
|
||||
1. Filter test-suite, options:
|
||||
- Config: Load first few lines, load into YAML, and check `config`, either:
|
||||
- Pass the suite to the correct compiled target
|
||||
- Ignore the suite if running tests as part of a compiled target with different configuration
|
||||
- Load the correct configuration for the suite dynamically before running the suite
|
||||
- Select by file name
|
||||
- Filter for specific suites (e.g. for a specific fork)
|
||||
2. Load the YAML
|
||||
- Optionally translate the data into applicable naming, e.g. `snake_case` to `PascalCase`
|
||||
3. Iterate through the `test_cases`
|
||||
4. Ask test-runner to allocate a new test-case (i.e. objectify the test-case, generalize it with a `TestCase` interface)
|
||||
Optionally pass raw test-case data to enable dynamic test-case allocation.
|
||||
1. Load test-case data into it.
|
||||
2. Make the test-case run.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# SSZ tests
|
||||
|
||||
SSZ has changed throughout the development of ETH 2.0.
|
||||
|
||||
## Contents
|
||||
|
||||
A minimal but useful series of tests covering `uint` encoding and decoding is provided.
|
||||
This is a direct port of the older SSZ `uint` tests (minus outdated test cases).
|
||||
|
||||
[uint test format](./uint.md).
|
||||
|
||||
Note: the current phase-0 spec does not use larger uints, and uses byte vectors (fixed length) instead to represent roots etc.
|
||||
The exact uint lengths to support may be redefined in the future.
|
||||
|
||||
Extension of the SSZ tests collection is planned, see CI/testing issues for progress tracking.
|
|
@ -0,0 +1,20 @@
|
|||
# SSZ, generic tests
|
||||
|
||||
This set of test-suites provides general testing for SSZ:
|
||||
to instantiate any container/list/vector/other type from binary data.
|
||||
|
||||
Since SSZ is in a development-phase, not the full suite of features is covered yet.
|
||||
Note that these tests are based on the older SSZ package.
|
||||
The tests are still relevant, but limited in scope:
|
||||
more complex object encodings have changed since the original SSZ testing.
|
||||
|
||||
A minimal but useful series of tests covering `uint` encoding and decoding is provided.
|
||||
This is a direct port of the older SSZ `uint` tests (minus outdated test cases).
|
||||
|
||||
[uint test format](./uint.md).
|
||||
|
||||
Note: the current phase-0 spec does not use larger uints, and uses byte vectors (fixed length) instead to represent roots etc.
|
||||
The exact uint lengths to support may be redefined in the future.
|
||||
|
||||
Extension of the SSZ tests collection is planned, with an update to the new spec-maintained `minimal_ssz.py`,
|
||||
see CI/testing issues for progress tracking.
|
|
@ -0,0 +1,8 @@
|
|||
# SSZ, static tests
|
||||
|
||||
This set of test-suites provides static testing for SSZ:
|
||||
to instantiate just the known ETH-2.0 SSZ types from binary data.
|
||||
|
||||
This series of tests is based on the spec-maintained `minimal_ssz.py`, i.e. fully consistent with the SSZ spec.
|
||||
|
||||
Test format documentation can be found here: [core test format](./core.md).
|
|
@ -0,0 +1,32 @@
|
|||
# Test format: SSZ static types
|
||||
|
||||
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.
|
||||
This test-format ensures these direct serializations are covered.
|
||||
|
||||
## Test case format
|
||||
|
||||
```yaml
|
||||
type_name: string -- string, object name, formatted as in spec. E.g. "BeaconBlock"
|
||||
value: dynamic -- the YAML-encoded value, of the type specified by type_name.
|
||||
serialized: bytes -- string, SSZ-serialized data, hex encoded, with prefix 0x
|
||||
root: bytes32 -- string, hash-tree-root of the value, hex encoded, with prefix 0x
|
||||
signing_root: bytes32 -- string, signing-root of the value, hex encoded, with prefix 0x. Optional, present if type contains ``signature`` field
|
||||
```
|
||||
|
||||
## Condition
|
||||
|
||||
A test-runner can implement the following assertions:
|
||||
- Serialization: After parsing the `value`, SSZ-serialize it: the output should match `serialized`
|
||||
- Hash-tree-root: After parsing the `value`, Hash-tree-root it: the output should match `root`
|
||||
- Optionally also check signing-root, if present.
|
||||
- Deserialization: SSZ-deserialize the `serialized` value, and see if it matches the parsed `value`
|
||||
|
||||
## References
|
||||
|
||||
|
||||
**`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
|
|
@ -60,7 +60,7 @@ __NOTICE__: This document is a work-in-progress for researchers and implementers
|
|||
|
||||
## Introduction
|
||||
|
||||
This document represents the expected behavior of an "honest validator" with respect to Phase 0 of the Ethereum 2.0 protocol. This document does not distinguish between a "node" (ie. the functionality of following and reading the beacon chain) and a "validator client" (ie. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope.
|
||||
This document represents the expected behavior of an "honest validator" with respect to Phase 0 of the Ethereum 2.0 protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope.
|
||||
|
||||
A validator is an entity that participates in the consensus of the Ethereum 2.0 protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof of work networks in which a miner provides collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol.
|
||||
|
||||
|
@ -101,11 +101,10 @@ In phase 0, all incoming validator deposits originate from the Ethereum 1.0 PoW
|
|||
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`.
|
||||
|
||||
|
@ -141,7 +140,7 @@ A validator has two primary responsibilities to the beacon chain -- [proposing b
|
|||
|
||||
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).
|
||||
|
||||
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 (eg. at 312500 validators = 10 million ETH, that's once per ~3 weeks).
|
||||
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312500 validators = 10 million ETH, that's once per ~3 weeks).
|
||||
|
||||
#### Block header
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -24,13 +24,12 @@ def build_deposit_data(state,
|
|||
pubkey=pubkey,
|
||||
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + withdrawal_cred[1:],
|
||||
amount=amount,
|
||||
proof_of_possession=spec.EMPTY_SIGNATURE,
|
||||
)
|
||||
deposit_data.proof_of_possession = bls.sign(
|
||||
message_hash=signing_root(deposit_data),
|
||||
privkey=privkey,
|
||||
domain=spec.get_domain(
|
||||
state.fork,
|
||||
state,
|
||||
spec.get_current_epoch(state),
|
||||
spec.DOMAIN_DEPOSIT,
|
||||
)
|
||||
|
@ -47,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))
|
||||
|
@ -70,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,
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -44,4 +44,4 @@ def ssz_uint_bounds_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gen_runner.run_generator("ssz", [ssz_random_uint_suite, ssz_wrong_uint_suite, ssz_uint_bounds_suite])
|
||||
gen_runner.run_generator("ssz_generic", [ssz_random_uint_suite, ssz_wrong_uint_suite, ssz_uint_bounds_suite])
|
|
@ -0,0 +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.
|
||||
|
||||
Test-format documentation can be found [here](../../specs/test_formats/ssz_static/README.md).
|
|
@ -0,0 +1,85 @@
|
|||
from random import Random
|
||||
|
||||
from eth2spec.debug import random_value, encode
|
||||
from eth2spec.phase0 import spec
|
||||
from eth2spec.utils.minimal_ssz import (
|
||||
hash_tree_root,
|
||||
signing_root,
|
||||
serialize,
|
||||
)
|
||||
from eth_utils import (
|
||||
to_tuple, to_dict
|
||||
)
|
||||
from gen_base import gen_runner, gen_suite, gen_typing
|
||||
from preset_loader import loader
|
||||
|
||||
MAX_BYTES_LENGTH = 100
|
||||
MAX_LIST_LENGTH = 10
|
||||
|
||||
|
||||
@to_dict
|
||||
def create_test_case(rng: Random, name: str, mode: random_value.RandomizationMode, chaos: bool):
|
||||
typ = spec.get_ssz_type_by_name(name)
|
||||
value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos)
|
||||
yield "type_name", name
|
||||
yield "value", encode.encode(value, typ)
|
||||
yield "serialized", '0x' + serialize(value).hex()
|
||||
yield "root", '0x' + hash_tree_root(value).hex()
|
||||
if hasattr(value, "signature"):
|
||||
yield "signing_root", '0x' + signing_root(value).hex()
|
||||
|
||||
|
||||
@to_tuple
|
||||
def ssz_static_cases(rng: Random, mode: random_value.RandomizationMode, chaos: bool, count: int):
|
||||
for type_name in spec.ssz_types:
|
||||
for i in range(count):
|
||||
yield create_test_case(rng, type_name, mode, chaos)
|
||||
|
||||
|
||||
def get_ssz_suite(seed: int, config_name: str, mode: random_value.RandomizationMode, chaos: bool, cases_if_random: int):
|
||||
def ssz_suite(configs_path: str) -> gen_typing.TestSuiteOutput:
|
||||
# Apply changes to presets, this affects some of the vector types.
|
||||
presets = loader.load_presets(configs_path, config_name)
|
||||
spec.apply_constants_preset(presets)
|
||||
|
||||
# Reproducible RNG
|
||||
rng = Random(seed)
|
||||
|
||||
random_mode_name = mode.to_name()
|
||||
|
||||
suite_name = f"ssz_{config_name}_{random_mode_name}{'_chaos' if chaos else ''}"
|
||||
|
||||
count = cases_if_random if chaos or mode.is_changing() else 1
|
||||
print(f"generating SSZ-static suite ({count} cases per ssz type): {suite_name}")
|
||||
|
||||
return (suite_name, "core", gen_suite.render_suite(
|
||||
title=f"ssz testing, with {config_name} config, randomized with mode {random_mode_name}{' and with chaos applied' if chaos else ''}",
|
||||
summary="Test suite for ssz serialization and hash-tree-root",
|
||||
forks_timeline="testing",
|
||||
forks=["phase0"],
|
||||
config=config_name,
|
||||
runner="ssz",
|
||||
handler="static",
|
||||
test_cases=ssz_static_cases(rng, mode, chaos, count)))
|
||||
|
||||
return ssz_suite
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# [(seed, config name, randomization mode, chaos on/off, cases_if_random)]
|
||||
settings = []
|
||||
seed = 1
|
||||
for mode in random_value.RandomizationMode:
|
||||
settings.append((seed, "minimal", mode, False, 30))
|
||||
seed += 1
|
||||
settings.append((seed, "minimal", random_value.RandomizationMode.mode_random, True, 30))
|
||||
seed += 1
|
||||
settings.append((seed, "mainnet", random_value.RandomizationMode.mode_random, False, 5))
|
||||
seed += 1
|
||||
|
||||
print("Settings: %d, SSZ-types: %d" % (len(settings), len(spec.ssz_types)))
|
||||
|
||||
gen_runner.run_generator("ssz_static", [
|
||||
get_ssz_suite(seed, config_name, mode, chaos, cases_if_random)
|
||||
for (seed, config_name, mode, chaos, cases_if_random) in settings
|
||||
])
|
|
@ -0,0 +1,4 @@
|
|||
eth-utils==1.4.1
|
||||
../../test_libs/gen_helpers
|
||||
../../test_libs/config_helpers
|
||||
../../test_libs/pyspec
|
|
@ -19,6 +19,8 @@ Or, to build a single file, specify the path, e.g. `make test_libs/pyspec/eth2sp
|
|||
|
||||
## Py-tests
|
||||
|
||||
After building, you can install the dependencies for running the `pyspec` tests with `make install_test`
|
||||
|
||||
These tests are not intended for client-consumption.
|
||||
These tests are sanity tests, to verify if the spec itself is consistent.
|
||||
|
||||
|
@ -38,8 +40,9 @@ python3 -m venv venv
|
|||
. venv/bin/activate
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
Note: make sure to run `make pyspec` from the root of the specs repository,
|
||||
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.
|
||||
The `-B` flag may be helpful to force-overwrite the `pyspec` output after you made a change to the markdown source files.
|
||||
|
||||
Run the tests:
|
||||
```
|
||||
|
@ -55,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.
|
||||
|
|
|
@ -3,6 +3,8 @@ from eth2spec.utils.minimal_ssz import hash_tree_root
|
|||
|
||||
def encode(value, typ, include_hash_tree_roots=False):
|
||||
if isinstance(typ, str) and typ[:4] == 'uint':
|
||||
if typ[4:] == '128' or typ[4:] == '256':
|
||||
return str(value)
|
||||
return value
|
||||
elif typ == 'bool':
|
||||
assert value in (True, False)
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
from random import Random
|
||||
from typing import Any
|
||||
from enum import Enum
|
||||
|
||||
|
||||
UINT_SIZES = [8, 16, 32, 64, 128, 256]
|
||||
|
||||
basic_types = ["uint%d" % v for v in UINT_SIZES] + ['bool', 'byte']
|
||||
|
||||
random_mode_names = ["random", "zero", "max", "nil", "one", "lengthy"]
|
||||
|
||||
|
||||
class RandomizationMode(Enum):
|
||||
# random content / length
|
||||
mode_random = 0
|
||||
# Zero-value
|
||||
mode_zero = 1
|
||||
# Maximum value, limited to count 1 however
|
||||
mode_max = 2
|
||||
# Return 0 values, i.e. empty
|
||||
mode_nil_count = 3
|
||||
# Return 1 value, random content
|
||||
mode_one_count = 4
|
||||
# Return max amount of values, random content
|
||||
mode_max_count = 5
|
||||
|
||||
def to_name(self):
|
||||
return random_mode_names[self.value]
|
||||
|
||||
def is_changing(self):
|
||||
return self.value in [0, 4, 5]
|
||||
|
||||
|
||||
def get_random_ssz_object(rng: Random, typ: Any, max_bytes_length: int, max_list_length: int, mode: RandomizationMode, chaos: bool) -> Any:
|
||||
"""
|
||||
Create an object for a given type, filled with random data.
|
||||
:param rng: The random number generator to use.
|
||||
:param typ: The type to instantiate
|
||||
:param max_bytes_length: the max. length for a random bytes array
|
||||
:param max_list_length: the max. length for a random list
|
||||
:param mode: how to randomize
|
||||
:param chaos: if true, the randomization-mode will be randomly changed
|
||||
:return: the random object instance, of the given type.
|
||||
"""
|
||||
if chaos:
|
||||
mode = rng.choice(list(RandomizationMode))
|
||||
if isinstance(typ, str):
|
||||
# Bytes array
|
||||
if typ == 'bytes':
|
||||
if mode == RandomizationMode.mode_nil_count:
|
||||
return b''
|
||||
if mode == RandomizationMode.mode_max_count:
|
||||
return get_random_bytes_list(rng, max_bytes_length)
|
||||
if mode == RandomizationMode.mode_one_count:
|
||||
return get_random_bytes_list(rng, 1)
|
||||
if mode == RandomizationMode.mode_zero:
|
||||
return b'\x00'
|
||||
if mode == RandomizationMode.mode_max:
|
||||
return b'\xff'
|
||||
return get_random_bytes_list(rng, rng.randint(0, max_bytes_length))
|
||||
elif typ[:5] == 'bytes' and len(typ) > 5:
|
||||
length = int(typ[5:])
|
||||
# Sanity, don't generate absurdly big random values
|
||||
# If a client is aiming to performance-test, they should create a benchmark suite.
|
||||
assert length <= max_bytes_length
|
||||
if mode == RandomizationMode.mode_zero:
|
||||
return b'\x00' * length
|
||||
if mode == RandomizationMode.mode_max:
|
||||
return b'\xff' * length
|
||||
return get_random_bytes_list(rng, length)
|
||||
# Basic types
|
||||
else:
|
||||
if mode == RandomizationMode.mode_zero:
|
||||
return get_min_basic_value(typ)
|
||||
if mode == RandomizationMode.mode_max:
|
||||
return get_max_basic_value(typ)
|
||||
return get_random_basic_value(rng, typ)
|
||||
# Vector:
|
||||
elif isinstance(typ, list) and len(typ) == 2:
|
||||
return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(typ[1])]
|
||||
# List:
|
||||
elif isinstance(typ, list) and len(typ) == 1:
|
||||
length = rng.randint(0, max_list_length)
|
||||
if mode == RandomizationMode.mode_one_count:
|
||||
length = 1
|
||||
if mode == RandomizationMode.mode_max_count:
|
||||
length = max_list_length
|
||||
return [get_random_ssz_object(rng, typ[0], max_bytes_length, max_list_length, mode, chaos) for _ in range(length)]
|
||||
# Container:
|
||||
elif hasattr(typ, 'fields'):
|
||||
return typ(**{field: get_random_ssz_object(rng, subtype, max_bytes_length, max_list_length, mode, chaos) for field, subtype in typ.fields.items()})
|
||||
else:
|
||||
print(typ)
|
||||
raise Exception("Type not recognized")
|
||||
|
||||
|
||||
def get_random_bytes_list(rng: Random, length: int) -> bytes:
|
||||
return bytes(rng.getrandbits(8) for _ in range(length))
|
||||
|
||||
|
||||
def get_random_basic_value(rng: Random, typ: str) -> Any:
|
||||
if typ == 'bool':
|
||||
return rng.choice((True, False))
|
||||
if typ[:4] == 'uint':
|
||||
size = int(typ[4:])
|
||||
assert size in UINT_SIZES
|
||||
return rng.randint(0, 2**size - 1)
|
||||
if typ == 'byte':
|
||||
return rng.randint(0, 8)
|
||||
else:
|
||||
raise ValueError("Not a basic type")
|
||||
|
||||
|
||||
def get_min_basic_value(typ: str) -> Any:
|
||||
if typ == 'bool':
|
||||
return False
|
||||
if typ[:4] == 'uint':
|
||||
size = int(typ[4:])
|
||||
assert size in UINT_SIZES
|
||||
return 0
|
||||
if typ == 'byte':
|
||||
return 0x00
|
||||
else:
|
||||
raise ValueError("Not a basic type")
|
||||
|
||||
|
||||
def get_max_basic_value(typ: str) -> Any:
|
||||
if typ == 'bool':
|
||||
return True
|
||||
if typ[:4] == 'uint':
|
||||
size = int(typ[4:])
|
||||
assert size in UINT_SIZES
|
||||
return 2**size - 1
|
||||
if typ == 'byte':
|
||||
return 0xff
|
||||
else:
|
||||
raise ValueError("Not a basic type")
|
|
@ -1,7 +1,7 @@
|
|||
from .hash_function import hash
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .hash_function import hash
|
||||
|
||||
BYTES_PER_CHUNK = 32
|
||||
BYTES_PER_LENGTH_PREFIX = 4
|
||||
ZERO_CHUNK = b'\x00' * BYTES_PER_CHUNK
|
||||
|
@ -17,10 +17,7 @@ def SSZType(fields):
|
|||
setattr(self, f, kwargs[f])
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.fields == other.fields and
|
||||
self.serialize() == other.serialize()
|
||||
)
|
||||
return self.fields == other.fields and self.serialize() == other.serialize()
|
||||
|
||||
def __hash__(self):
|
||||
return int.from_bytes(self.hash_tree_root(), byteorder="little")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
from copy import deepcopy
|
||||
import pytest
|
||||
|
||||
import eth2spec.phase0.spec as spec
|
||||
|
||||
from eth2spec.phase0.spec import (
|
||||
get_active_validator_indices,
|
||||
get_balance,
|
||||
get_beacon_proposer_index,
|
||||
get_current_epoch,
|
||||
process_transfer,
|
||||
set_balance,
|
||||
)
|
||||
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
|
||||
set_balance(state, 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
|
||||
set_balance(state, 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
|
||||
set_balance(state, 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
|
|
@ -9,28 +9,29 @@ import eth2spec.phase0.spec as spec
|
|||
from eth2spec.utils.minimal_ssz import signing_root
|
||||
from eth2spec.phase0.spec import (
|
||||
# constants
|
||||
EMPTY_SIGNATURE,
|
||||
ZERO_HASH,
|
||||
# SSZ
|
||||
Attestation,
|
||||
AttestationData,
|
||||
AttestationDataAndCustodyBit,
|
||||
AttesterSlashing,
|
||||
BeaconBlock,
|
||||
BeaconBlockHeader,
|
||||
Deposit,
|
||||
DepositData,
|
||||
Eth1Data,
|
||||
ProposerSlashing,
|
||||
Transfer,
|
||||
VoluntaryExit,
|
||||
# functions
|
||||
convert_to_indexed,
|
||||
get_active_validator_indices,
|
||||
get_balance,
|
||||
get_attesting_indices,
|
||||
get_block_root,
|
||||
get_crosslink_committees_at_slot,
|
||||
get_current_epoch,
|
||||
get_domain,
|
||||
get_empty_block,
|
||||
get_epoch_start_slot,
|
||||
get_genesis_beacon_state,
|
||||
get_previous_epoch,
|
||||
|
@ -68,7 +69,7 @@ def set_bitfield_bit(bitfield, i):
|
|||
def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None):
|
||||
if not deposit_data_leaves:
|
||||
deposit_data_leaves = []
|
||||
proof_of_possession = b'\x33' * 96
|
||||
signature = b'\x33' * 96
|
||||
|
||||
deposit_data_list = []
|
||||
for i in range(num_validators):
|
||||
|
@ -77,10 +78,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,
|
||||
proof_of_possession=proof_of_possession,
|
||||
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)))
|
||||
|
@ -115,7 +116,7 @@ def create_genesis_state(num_validators, deposit_data_leaves=None):
|
|||
|
||||
|
||||
def build_empty_block_for_next_slot(state):
|
||||
empty_block = get_empty_block()
|
||||
empty_block = BeaconBlock()
|
||||
empty_block.slot = state.slot + 1
|
||||
previous_block_header = deepcopy(state.latest_block_header)
|
||||
if previous_block_header.state_root == spec.ZERO_HASH:
|
||||
|
@ -130,18 +131,16 @@ def build_deposit_data(state, pubkey, privkey, amount):
|
|||
# insecurely use pubkey as withdrawal key as well
|
||||
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
|
||||
amount=amount,
|
||||
proof_of_possession=EMPTY_SIGNATURE,
|
||||
)
|
||||
proof_of_possession = bls.sign(
|
||||
signature = bls.sign(
|
||||
message_hash=signing_root(deposit_data),
|
||||
privkey=privkey,
|
||||
domain=get_domain(
|
||||
state.fork,
|
||||
get_current_epoch(state),
|
||||
state,
|
||||
spec.DOMAIN_DEPOSIT,
|
||||
)
|
||||
)
|
||||
deposit_data.proof_of_possession = proof_of_possession
|
||||
deposit_data.signature = signature
|
||||
return deposit_data
|
||||
|
||||
|
||||
|
@ -185,15 +184,14 @@ def build_voluntary_exit(state, epoch, validator_index, privkey):
|
|||
voluntary_exit = VoluntaryExit(
|
||||
epoch=epoch,
|
||||
validator_index=validator_index,
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
voluntary_exit.signature = bls.sign(
|
||||
message_hash=signing_root(voluntary_exit),
|
||||
privkey=privkey,
|
||||
domain=get_domain(
|
||||
fork=state.fork,
|
||||
epoch=epoch,
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
|
||||
message_epoch=epoch,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -207,7 +205,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))
|
||||
|
@ -235,16 +233,14 @@ def get_valid_proposer_slashing(state):
|
|||
previous_block_root=ZERO_HASH,
|
||||
state_root=ZERO_HASH,
|
||||
block_body_root=ZERO_HASH,
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
header_2 = deepcopy(header_1)
|
||||
header_2.previous_block_root = b'\x02' * 32
|
||||
header_2.slot = slot + 1
|
||||
|
||||
domain = get_domain(
|
||||
fork=state.fork,
|
||||
epoch=get_current_epoch(state),
|
||||
domain_type=spec.DOMAIN_BEACON_BLOCK,
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_BEACON_PROPOSER,
|
||||
)
|
||||
header_1.signature = bls.sign(
|
||||
message_hash=signing_root(header_1),
|
||||
|
@ -305,7 +301,6 @@ def get_valid_attestation(state, slot=None):
|
|||
aggregation_bitfield=aggregation_bitfield,
|
||||
data=attestation_data,
|
||||
custody_bitfield=custody_bitfield,
|
||||
aggregate_signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
participants = get_attesting_indices(
|
||||
state,
|
||||
|
@ -329,6 +324,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,
|
||||
|
@ -339,9 +376,9 @@ def get_attestation_signature(state, attestation_data, privkey, custody_bit=0b0)
|
|||
message_hash=message_hash,
|
||||
privkey=privkey,
|
||||
domain=get_domain(
|
||||
fork=state.fork,
|
||||
epoch=slot_to_epoch(attestation_data.slot),
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_ATTESTATION,
|
||||
message_epoch=slot_to_epoch(attestation_data.slot),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import eth2spec.phase0.spec as spec
|
|||
from eth2spec.utils.minimal_ssz import signing_root
|
||||
from eth2spec.phase0.spec import (
|
||||
# constants
|
||||
EMPTY_SIGNATURE,
|
||||
ZERO_HASH,
|
||||
# SSZ
|
||||
Deposit,
|
||||
|
@ -207,9 +206,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)))
|
||||
|
@ -231,7 +230,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
|
||||
|
@ -242,13 +241,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)))
|
||||
|
@ -323,14 +322,12 @@ def test_voluntary_exit(state):
|
|||
voluntary_exit = VoluntaryExit(
|
||||
epoch=get_current_epoch(pre_state),
|
||||
validator_index=validator_index,
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
voluntary_exit.signature = bls.sign(
|
||||
message_hash=signing_root(voluntary_exit),
|
||||
privkey=privkeys[validator_index],
|
||||
domain=get_domain(
|
||||
fork=pre_state.fork,
|
||||
epoch=get_current_epoch(pre_state),
|
||||
state=pre_state,
|
||||
domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
|
||||
)
|
||||
)
|
||||
|
@ -357,6 +354,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]
|
||||
|
@ -372,14 +372,12 @@ def test_transfer(state):
|
|||
fee=0,
|
||||
slot=pre_state.slot + 1,
|
||||
pubkey=transfer_pubkey,
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
transfer.signature = bls.sign(
|
||||
message_hash=signing_root(transfer),
|
||||
privkey=transfer_privkey,
|
||||
domain=get_domain(
|
||||
fork=pre_state.fork,
|
||||
epoch=get_current_epoch(pre_state),
|
||||
state=pre_state,
|
||||
domain_type=spec.DOMAIN_TRANSFER,
|
||||
)
|
||||
)
|
||||
|
@ -389,7 +387,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)
|
||||
#
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
from copy import deepcopy
|
||||
import pytest
|
||||
|
||||
|
||||
from build.phase0.spec import (
|
||||
get_beacon_proposer_index,
|
||||
cache_state,
|
||||
advance_slot,
|
||||
process_block_header,
|
||||
)
|
||||
from tests.phase0.helpers import (
|
||||
build_empty_block_for_next_slot,
|
||||
)
|
||||
|
||||
# mark entire file as 'header'
|
||||
pytestmark = pytest.mark.header
|
||||
|
||||
|
||||
def prepare_state_for_header_processing(state):
|
||||
cache_state(state)
|
||||
advance_slot(state)
|
||||
|
||||
|
||||
def run_block_header_processing(state, block, valid=True):
|
||||
"""
|
||||
Run ``process_block_header`` returning the pre and post state.
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
prepare_state_for_header_processing(state)
|
||||
post_state = deepcopy(state)
|
||||
|
||||
if not valid:
|
||||
with pytest.raises(AssertionError):
|
||||
process_block_header(post_state, block)
|
||||
return state, None
|
||||
|
||||
process_block_header(post_state, block)
|
||||
return state, post_state
|
||||
|
||||
|
||||
def test_success(state):
|
||||
block = build_empty_block_for_next_slot(state)
|
||||
pre_state, post_state = run_block_header_processing(state, block)
|
||||
return state, block, post_state
|
||||
|
||||
|
||||
def test_invalid_slot(state):
|
||||
block = build_empty_block_for_next_slot(state)
|
||||
block.slot = state.slot + 2 # invalid slot
|
||||
|
||||
pre_state, post_state = run_block_header_processing(state, block, valid=False)
|
||||
return pre_state, block, None
|
||||
|
||||
|
||||
def test_invalid_previous_block_root(state):
|
||||
block = build_empty_block_for_next_slot(state)
|
||||
block.previous_block_root = b'\12'*32 # invalid prev root
|
||||
|
||||
pre_state, post_state = run_block_header_processing(state, block, valid=False)
|
||||
return pre_state, block, None
|
|
@ -0,0 +1,140 @@
|
|||
from copy import deepcopy
|
||||
import pytest
|
||||
|
||||
import build.phase0.spec as spec
|
||||
|
||||
from build.phase0.spec import (
|
||||
ZERO_HASH,
|
||||
process_deposit,
|
||||
)
|
||||
from tests.phase0.helpers import (
|
||||
build_deposit,
|
||||
privkeys,
|
||||
pubkeys,
|
||||
)
|
||||
|
||||
|
||||
# mark entire file as 'voluntary_exits'
|
||||
pytestmark = pytest.mark.voluntary_exits
|
||||
|
||||
|
||||
def test_success(state):
|
||||
pre_state = deepcopy(state)
|
||||
# fill previous deposits with zero-hash
|
||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
||||
|
||||
index = len(deposit_data_leaves)
|
||||
pubkey = pubkeys[index]
|
||||
privkey = privkeys[index]
|
||||
deposit, root, deposit_data_leaves = build_deposit(
|
||||
pre_state,
|
||||
deposit_data_leaves,
|
||||
pubkey,
|
||||
privkey,
|
||||
spec.MAX_DEPOSIT_AMOUNT,
|
||||
)
|
||||
|
||||
pre_state.latest_eth1_data.deposit_root = root
|
||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
||||
|
||||
post_state = deepcopy(pre_state)
|
||||
|
||||
process_deposit(post_state, deposit)
|
||||
|
||||
assert len(post_state.validator_registry) == len(state.validator_registry) + 1
|
||||
assert len(post_state.validator_balances) == len(state.validator_balances) + 1
|
||||
assert post_state.validator_registry[index].pubkey == pubkeys[index]
|
||||
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
|
||||
|
||||
return pre_state, deposit, post_state
|
||||
|
||||
|
||||
def test_success_top_up(state):
|
||||
pre_state = deepcopy(state)
|
||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
||||
|
||||
validator_index = 0
|
||||
amount = spec.MAX_DEPOSIT_AMOUNT // 4
|
||||
pubkey = pubkeys[validator_index]
|
||||
privkey = privkeys[validator_index]
|
||||
deposit, root, deposit_data_leaves = build_deposit(
|
||||
pre_state,
|
||||
deposit_data_leaves,
|
||||
pubkey,
|
||||
privkey,
|
||||
amount,
|
||||
)
|
||||
|
||||
pre_state.latest_eth1_data.deposit_root = root
|
||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
||||
pre_balance = pre_state.validator_balances[validator_index]
|
||||
|
||||
post_state = deepcopy(pre_state)
|
||||
|
||||
process_deposit(post_state, deposit)
|
||||
|
||||
assert len(post_state.validator_registry) == len(state.validator_registry)
|
||||
assert len(post_state.validator_balances) == len(state.validator_balances)
|
||||
assert post_state.deposit_index == post_state.latest_eth1_data.deposit_count
|
||||
assert post_state.validator_balances[validator_index] == pre_balance + amount
|
||||
|
||||
return pre_state, deposit, post_state
|
||||
|
||||
|
||||
def test_wrong_index(state):
|
||||
pre_state = deepcopy(state)
|
||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
||||
|
||||
|
||||
index = len(deposit_data_leaves)
|
||||
pubkey = pubkeys[index]
|
||||
privkey = privkeys[index]
|
||||
deposit, root, deposit_data_leaves = build_deposit(
|
||||
pre_state,
|
||||
deposit_data_leaves,
|
||||
pubkey,
|
||||
privkey,
|
||||
spec.MAX_DEPOSIT_AMOUNT,
|
||||
)
|
||||
|
||||
# mess up deposit_index
|
||||
deposit.index = pre_state.deposit_index + 1
|
||||
|
||||
pre_state.latest_eth1_data.deposit_root = root
|
||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
||||
|
||||
post_state = deepcopy(pre_state)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
process_deposit(post_state, deposit)
|
||||
|
||||
return pre_state, deposit, None
|
||||
|
||||
|
||||
def test_bad_merkle_proof(state):
|
||||
pre_state = deepcopy(state)
|
||||
deposit_data_leaves = [ZERO_HASH] * len(pre_state.validator_registry)
|
||||
|
||||
index = len(deposit_data_leaves)
|
||||
pubkey = pubkeys[index]
|
||||
privkey = privkeys[index]
|
||||
deposit, root, deposit_data_leaves = build_deposit(
|
||||
pre_state,
|
||||
deposit_data_leaves,
|
||||
pubkey,
|
||||
privkey,
|
||||
spec.MAX_DEPOSIT_AMOUNT,
|
||||
)
|
||||
|
||||
# mess up merkle branch
|
||||
deposit.proof[-1] = spec.ZERO_HASH
|
||||
|
||||
pre_state.latest_eth1_data.deposit_root = root
|
||||
pre_state.latest_eth1_data.deposit_count = len(deposit_data_leaves)
|
||||
|
||||
post_state = deepcopy(pre_state)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
process_deposit(post_state, deposit)
|
||||
|
||||
return pre_state, deposit, None
|
|
@ -0,0 +1,175 @@
|
|||
from copy import deepcopy
|
||||
import pytest
|
||||
|
||||
import build.phase0.spec as spec
|
||||
|
||||
from build.phase0.spec import (
|
||||
get_active_validator_indices,
|
||||
get_current_epoch,
|
||||
process_voluntary_exit,
|
||||
)
|
||||
from tests.phase0.helpers import (
|
||||
build_voluntary_exit,
|
||||
pubkey_to_privkey,
|
||||
)
|
||||
|
||||
|
||||
# mark entire file as 'voluntary_exits'
|
||||
pytestmark = pytest.mark.voluntary_exits
|
||||
|
||||
|
||||
def test_success(state):
|
||||
pre_state = deepcopy(state)
|
||||
#
|
||||
# setup pre_state
|
||||
#
|
||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
|
||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
||||
|
||||
#
|
||||
# build voluntary exit
|
||||
#
|
||||
current_epoch = get_current_epoch(pre_state)
|
||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
||||
|
||||
voluntary_exit = build_voluntary_exit(
|
||||
pre_state,
|
||||
current_epoch,
|
||||
validator_index,
|
||||
privkey,
|
||||
)
|
||||
|
||||
post_state = deepcopy(pre_state)
|
||||
|
||||
#
|
||||
# test valid exit
|
||||
#
|
||||
process_voluntary_exit(post_state, voluntary_exit)
|
||||
|
||||
assert not pre_state.validator_registry[validator_index].initiated_exit
|
||||
assert post_state.validator_registry[validator_index].initiated_exit
|
||||
|
||||
return pre_state, voluntary_exit, post_state
|
||||
|
||||
|
||||
def test_validator_not_active(state):
|
||||
pre_state = deepcopy(state)
|
||||
current_epoch = get_current_epoch(pre_state)
|
||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
||||
|
||||
#
|
||||
# setup pre_state
|
||||
#
|
||||
pre_state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH
|
||||
|
||||
#
|
||||
# build and test voluntary exit
|
||||
#
|
||||
voluntary_exit = build_voluntary_exit(
|
||||
pre_state,
|
||||
current_epoch,
|
||||
validator_index,
|
||||
privkey,
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
process_voluntary_exit(pre_state, voluntary_exit)
|
||||
|
||||
return pre_state, voluntary_exit, None
|
||||
|
||||
|
||||
def test_validator_already_exited(state):
|
||||
pre_state = deepcopy(state)
|
||||
#
|
||||
# setup pre_state
|
||||
#
|
||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
|
||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
||||
|
||||
current_epoch = get_current_epoch(pre_state)
|
||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
||||
|
||||
# but validator already has exited
|
||||
pre_state.validator_registry[validator_index].exit_epoch = current_epoch + 2
|
||||
|
||||
#
|
||||
# build voluntary exit
|
||||
#
|
||||
voluntary_exit = build_voluntary_exit(
|
||||
pre_state,
|
||||
current_epoch,
|
||||
validator_index,
|
||||
privkey,
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
process_voluntary_exit(pre_state, voluntary_exit)
|
||||
|
||||
return pre_state, voluntary_exit, None
|
||||
|
||||
|
||||
def test_validator_already_initiated_exit(state):
|
||||
pre_state = deepcopy(state)
|
||||
#
|
||||
# setup pre_state
|
||||
#
|
||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit
|
||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
||||
|
||||
current_epoch = get_current_epoch(pre_state)
|
||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
||||
|
||||
# but validator already has initiated exit
|
||||
pre_state.validator_registry[validator_index].initiated_exit = True
|
||||
|
||||
#
|
||||
# build voluntary exit
|
||||
#
|
||||
voluntary_exit = build_voluntary_exit(
|
||||
pre_state,
|
||||
current_epoch,
|
||||
validator_index,
|
||||
privkey,
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
process_voluntary_exit(pre_state, voluntary_exit)
|
||||
|
||||
return pre_state, voluntary_exit, None
|
||||
|
||||
|
||||
def test_validator_not_active_long_enough(state):
|
||||
pre_state = deepcopy(state)
|
||||
#
|
||||
# setup pre_state
|
||||
#
|
||||
current_epoch = get_current_epoch(pre_state)
|
||||
validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0]
|
||||
privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey]
|
||||
|
||||
# but validator already has initiated exit
|
||||
pre_state.validator_registry[validator_index].initiated_exit = True
|
||||
|
||||
#
|
||||
# build voluntary exit
|
||||
#
|
||||
voluntary_exit = build_voluntary_exit(
|
||||
pre_state,
|
||||
current_epoch,
|
||||
validator_index,
|
||||
privkey,
|
||||
)
|
||||
|
||||
assert (
|
||||
current_epoch - pre_state.validator_registry[validator_index].activation_epoch <
|
||||
spec.PERSISTENT_COMMITTEE_PERIOD
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
process_voluntary_exit(pre_state, voluntary_exit)
|
||||
|
||||
return pre_state, voluntary_exit, None
|
|
@ -0,0 +1,203 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from py_ecc import bls
|
||||
|
||||
import build.phase0.spec as spec
|
||||
from build.phase0.utils.minimal_ssz import signed_root
|
||||
from build.phase0.spec import (
|
||||
# constants
|
||||
EMPTY_SIGNATURE,
|
||||
# SSZ
|
||||
AttestationData,
|
||||
Deposit,
|
||||
DepositInput,
|
||||
DepositData,
|
||||
Eth1Data,
|
||||
VoluntaryExit,
|
||||
# functions
|
||||
get_block_root,
|
||||
get_current_epoch,
|
||||
get_domain,
|
||||
get_empty_block,
|
||||
get_epoch_start_slot,
|
||||
get_genesis_beacon_state,
|
||||
verify_merkle_branch,
|
||||
hash,
|
||||
)
|
||||
from build.phase0.utils.merkle_minimal import (
|
||||
calc_merkle_tree_from_leaves,
|
||||
get_merkle_proof,
|
||||
get_merkle_root,
|
||||
)
|
||||
|
||||
|
||||
privkeys = [i + 1 for i in range(1000)]
|
||||
pubkeys = [bls.privtopub(privkey) for privkey in privkeys]
|
||||
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
|
||||
|
||||
|
||||
def create_mock_genesis_validator_deposits(num_validators, deposit_data_leaves=None):
|
||||
if not deposit_data_leaves:
|
||||
deposit_data_leaves = []
|
||||
deposit_timestamp = 0
|
||||
proof_of_possession = b'\x33' * 96
|
||||
|
||||
deposit_data_list = []
|
||||
for i in range(num_validators):
|
||||
pubkey = pubkeys[i]
|
||||
deposit_data = DepositData(
|
||||
amount=spec.MAX_DEPOSIT_AMOUNT,
|
||||
timestamp=deposit_timestamp,
|
||||
deposit_input=DepositInput(
|
||||
pubkey=pubkey,
|
||||
# insecurely use pubkey as withdrawal key as well
|
||||
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
|
||||
proof_of_possession=proof_of_possession,
|
||||
),
|
||||
)
|
||||
item = hash(deposit_data.serialize())
|
||||
deposit_data_leaves.append(item)
|
||||
tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
|
||||
root = get_merkle_root((tuple(deposit_data_leaves)))
|
||||
proof = list(get_merkle_proof(tree, item_index=i))
|
||||
assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, i, root)
|
||||
deposit_data_list.append(deposit_data)
|
||||
|
||||
genesis_validator_deposits = []
|
||||
for i in range(num_validators):
|
||||
genesis_validator_deposits.append(Deposit(
|
||||
proof=list(get_merkle_proof(tree, item_index=i)),
|
||||
index=i,
|
||||
deposit_data=deposit_data_list[i]
|
||||
))
|
||||
return genesis_validator_deposits, root
|
||||
|
||||
|
||||
def create_genesis_state(num_validators, deposit_data_leaves=None):
|
||||
initial_deposits, deposit_root = create_mock_genesis_validator_deposits(
|
||||
num_validators,
|
||||
deposit_data_leaves,
|
||||
)
|
||||
return get_genesis_beacon_state(
|
||||
initial_deposits,
|
||||
genesis_time=0,
|
||||
genesis_eth1_data=Eth1Data(
|
||||
deposit_root=deposit_root,
|
||||
deposit_count=len(initial_deposits),
|
||||
block_hash=spec.ZERO_HASH,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def force_registry_change_at_next_epoch(state):
|
||||
# artificially trigger registry update at next epoch transition
|
||||
state.finalized_epoch = get_current_epoch(state) - 1
|
||||
for crosslink in state.latest_crosslinks:
|
||||
crosslink.epoch = state.finalized_epoch
|
||||
state.validator_registry_update_epoch = state.finalized_epoch - 1
|
||||
|
||||
|
||||
def build_empty_block_for_next_slot(state):
|
||||
empty_block = get_empty_block()
|
||||
empty_block.slot = state.slot + 1
|
||||
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()
|
||||
empty_block.previous_block_root = signed_root(previous_block_header)
|
||||
return empty_block
|
||||
|
||||
|
||||
def build_deposit_data(state, pubkey, privkey, amount):
|
||||
deposit_input = DepositInput(
|
||||
pubkey=pubkey,
|
||||
# insecurely use pubkey as withdrawal key as well
|
||||
withdrawal_credentials=spec.BLS_WITHDRAWAL_PREFIX_BYTE + hash(pubkey)[1:],
|
||||
proof_of_possession=EMPTY_SIGNATURE,
|
||||
)
|
||||
proof_of_possession = bls.sign(
|
||||
message_hash=signed_root(deposit_input),
|
||||
privkey=privkey,
|
||||
domain=get_domain(
|
||||
state.fork,
|
||||
get_current_epoch(state),
|
||||
spec.DOMAIN_DEPOSIT,
|
||||
)
|
||||
)
|
||||
deposit_input.proof_of_possession = proof_of_possession
|
||||
deposit_data = DepositData(
|
||||
amount=amount,
|
||||
timestamp=0,
|
||||
deposit_input=deposit_input,
|
||||
)
|
||||
return deposit_data
|
||||
|
||||
|
||||
def build_attestation_data(state, slot, shard):
|
||||
assert state.slot >= slot
|
||||
|
||||
block_root = build_empty_block_for_next_slot(state).previous_block_root
|
||||
|
||||
epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
||||
if epoch_start_slot == slot:
|
||||
epoch_boundary_root = block_root
|
||||
else:
|
||||
get_block_root(state, epoch_start_slot)
|
||||
|
||||
if slot < epoch_start_slot:
|
||||
justified_block_root = state.previous_justified_root
|
||||
else:
|
||||
justified_block_root = state.current_justified_root
|
||||
|
||||
return AttestationData(
|
||||
slot=slot,
|
||||
shard=shard,
|
||||
beacon_block_root=block_root,
|
||||
source_epoch=state.current_justified_epoch,
|
||||
source_root=justified_block_root,
|
||||
target_root=epoch_boundary_root,
|
||||
crosslink_data_root=spec.ZERO_HASH,
|
||||
previous_crosslink=deepcopy(state.latest_crosslinks[shard]),
|
||||
)
|
||||
|
||||
|
||||
def build_voluntary_exit(state, epoch, validator_index, privkey):
|
||||
voluntary_exit = VoluntaryExit(
|
||||
epoch=epoch,
|
||||
validator_index=validator_index,
|
||||
signature=EMPTY_SIGNATURE,
|
||||
)
|
||||
voluntary_exit.signature = bls.sign(
|
||||
message_hash=signed_root(voluntary_exit),
|
||||
privkey=privkey,
|
||||
domain=get_domain(
|
||||
fork=state.fork,
|
||||
epoch=epoch,
|
||||
domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
|
||||
)
|
||||
)
|
||||
|
||||
return voluntary_exit
|
||||
|
||||
|
||||
def build_deposit(state,
|
||||
deposit_data_leaves,
|
||||
pubkey,
|
||||
privkey,
|
||||
amount):
|
||||
deposit_data = build_deposit_data(state, pubkey, privkey, amount)
|
||||
|
||||
item = hash(deposit_data.serialize())
|
||||
index = len(deposit_data_leaves)
|
||||
deposit_data_leaves.append(item)
|
||||
tree = calc_merkle_tree_from_leaves(tuple(deposit_data_leaves))
|
||||
root = get_merkle_root((tuple(deposit_data_leaves)))
|
||||
proof = list(get_merkle_proof(tree, item_index=index))
|
||||
assert verify_merkle_branch(item, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH, index, root)
|
||||
|
||||
deposit = Deposit(
|
||||
proof=list(proof),
|
||||
index=index,
|
||||
deposit_data=deposit_data,
|
||||
)
|
||||
|
||||
return deposit, root, deposit_data_leaves
|
|
@ -0,0 +1,52 @@
|
|||
from .minimal_ssz import hash_tree_root
|
||||
|
||||
|
||||
def jsonize(value, typ, include_hash_tree_roots=False):
|
||||
if isinstance(typ, str) and typ[:4] == 'uint':
|
||||
return value
|
||||
elif typ == 'bool':
|
||||
assert value in (True, False)
|
||||
return value
|
||||
elif isinstance(typ, list):
|
||||
return [jsonize(element, typ[0], include_hash_tree_roots) for element in value]
|
||||
elif isinstance(typ, str) and typ[:4] == 'byte':
|
||||
return '0x' + value.hex()
|
||||
elif hasattr(typ, 'fields'):
|
||||
ret = {}
|
||||
for field, subtype in typ.fields.items():
|
||||
ret[field] = jsonize(getattr(value, field), subtype, include_hash_tree_roots)
|
||||
if include_hash_tree_roots:
|
||||
ret[field + "_hash_tree_root"] = '0x' + hash_tree_root(getattr(value, field), subtype).hex()
|
||||
if include_hash_tree_roots:
|
||||
ret["hash_tree_root"] = '0x' + hash_tree_root(value, typ).hex()
|
||||
return ret
|
||||
else:
|
||||
print(value, typ)
|
||||
raise Exception("Type not recognized")
|
||||
|
||||
|
||||
def dejsonize(json, typ):
|
||||
if isinstance(typ, str) and typ[:4] == 'uint':
|
||||
return json
|
||||
elif typ == 'bool':
|
||||
assert json in (True, False)
|
||||
return json
|
||||
elif isinstance(typ, list):
|
||||
return [dejsonize(element, typ[0]) for element in json]
|
||||
elif isinstance(typ, str) and typ[:4] == 'byte':
|
||||
return bytes.fromhex(json[2:])
|
||||
elif hasattr(typ, 'fields'):
|
||||
temp = {}
|
||||
for field, subtype in typ.fields.items():
|
||||
temp[field] = dejsonize(json[field], subtype)
|
||||
if field + "_hash_tree_root" in json:
|
||||
assert(json[field + "_hash_tree_root"][2:] ==
|
||||
hash_tree_root(temp[field], subtype).hex())
|
||||
ret = typ(**temp)
|
||||
if "hash_tree_root" in json:
|
||||
assert(json["hash_tree_root"][2:] ==
|
||||
hash_tree_root(ret, typ).hex())
|
||||
return ret
|
||||
else:
|
||||
print(json, typ)
|
||||
raise Exception("Type not recognized")
|
Loading…
Reference in New Issue