Merge pull request #1561 from ethereum/dev

Release v0.10 to master
This commit is contained in:
Danny Ryan 2020-01-10 17:40:20 -07:00 committed by GitHub
commit 2e3fcc16f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
180 changed files with 611 additions and 724 deletions

View File

@ -35,27 +35,27 @@ commands:
description: "Restore the cache with pyspec keys" description: "Restore the cache with pyspec keys"
steps: steps:
- restore_cached_venv: - restore_cached_venv:
venv_name: v4-pyspec venv_name: v7-pyspec
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }} reqs_checksum: cache-{{ checksum "tests/core/pyspec/requirements.txt" }}-{{ checksum "tests/core/pyspec/requirements-testing.txt" }}
save_pyspec_cached_venv: save_pyspec_cached_venv:
description: Save a venv into a cache with pyspec keys" description: Save a venv into a cache with pyspec keys"
steps: steps:
- save_cached_venv: - save_cached_venv:
venv_name: v4-pyspec venv_name: v7-pyspec
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "test_libs/pyspec/requirements-testing.txt" }} reqs_checksum: cache-{{ checksum "tests/core/pyspec/requirements.txt" }}-{{ checksum "tests/core/pyspec/requirements-testing.txt" }}
venv_path: ./test_libs/pyspec/venv venv_path: ./tests/core/pyspec/venv
restore_deposit_contract_cached_venv: restore_deposit_contract_cached_venv:
description: "Restore the cache with deposit_contract keys" description: "Restore the cache with deposit_contract keys"
steps: steps:
- restore_cached_venv: - restore_cached_venv:
venv_name: v6-deposit-contract venv_name: v9-deposit-contract
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }} reqs_checksum: cache-{{ checksum "tests/core/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }}
save_deposit_contract_cached_venv: save_deposit_contract_cached_venv:
description: Save a venv into a cache with deposit_contract keys" description: Save a venv into a cache with deposit_contract keys"
steps: steps:
- save_cached_venv: - save_cached_venv:
venv_name: v6-deposit-contract venv_name: v9-deposit-contract
reqs_checksum: cache-{{ checksum "test_libs/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }} reqs_checksum: cache-{{ checksum "tests/core/pyspec/requirements.txt" }}-{{ checksum "deposit_contract/requirements-testing.txt" }}
venv_path: ./deposit_contract/venv venv_path: ./deposit_contract/venv
jobs: jobs:
checkout_specs: checkout_specs:
@ -66,16 +66,16 @@ jobs:
# Restore git repo at point close to target branch/revision, to speed up checkout # Restore git repo at point close to target branch/revision, to speed up checkout
- restore_cache: - restore_cache:
keys: keys:
- v1-specs-repo-{{ .Branch }}-{{ .Revision }} - v2-specs-repo-{{ .Branch }}-{{ .Revision }}
- v1-specs-repo-{{ .Branch }}- - v2-specs-repo-{{ .Branch }}-
- v1-specs-repo- - v2-specs-repo-
- checkout - checkout
- run: - run:
name: Clean up git repo to reduce cache size name: Clean up git repo to reduce cache size
command: git gc command: git gc
# Save the git checkout as a cache, to make cloning next time faster. # Save the git checkout as a cache, to make cloning next time faster.
- save_cache: - save_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v2-specs-repo-{{ .Branch }}-{{ .Revision }}
paths: paths:
- ~/specs-repo - ~/specs-repo
install_pyspec_test: install_pyspec_test:
@ -84,7 +84,7 @@ jobs:
working_directory: ~/specs-repo working_directory: ~/specs-repo
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v2-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv - restore_pyspec_cached_venv
- run: - run:
name: Install pyspec requirements name: Install pyspec requirements
@ -96,13 +96,13 @@ jobs:
working_directory: ~/specs-repo working_directory: ~/specs-repo
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v2-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv - restore_pyspec_cached_venv
- run: - run:
name: Run py-tests name: Run py-tests
command: make citest command: make citest
- store_test_results: - store_test_results:
path: test_libs/pyspec/test-reports path: tests/core/pyspec/test-reports
table_of_contents: table_of_contents:
docker: docker:
- image: circleci/node:10.16.3 - image: circleci/node:10.16.3
@ -127,7 +127,7 @@ jobs:
working_directory: ~/specs-repo working_directory: ~/specs-repo
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v2-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv - restore_pyspec_cached_venv
- run: - run:
name: Run linter name: Run linter
@ -138,7 +138,7 @@ jobs:
working_directory: ~/specs-repo working_directory: ~/specs-repo
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v2-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_cached_venv - restore_deposit_contract_cached_venv
- run: - run:
name: Install deposit contract requirements name: Install deposit contract requirements
@ -150,7 +150,7 @@ jobs:
working_directory: ~/specs-repo working_directory: ~/specs-repo
steps: steps:
- restore_cache: - restore_cache:
key: v1-specs-repo-{{ .Branch }}-{{ .Revision }} key: v2-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_cached_venv - restore_deposit_contract_cached_venv
- run: - run:
name: Run deposit contract test name: Run deposit contract test

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.vy linguist-language=Python

6
.gitignore vendored
View File

@ -14,12 +14,12 @@ eth2.0-spec-tests/
.mypy_cache .mypy_cache
# Dynamically built from Markdown spec # Dynamically built from Markdown spec
test_libs/pyspec/eth2spec/phase0/spec.py tests/core/pyspec/eth2spec/phase0/spec.py
test_libs/pyspec/eth2spec/phase1/spec.py tests/core/pyspec/eth2spec/phase1/spec.py
# coverage reports # coverage reports
.htmlcov .htmlcov
.coverage .coverage
# local CI testing output # local CI testing output
test_libs/pyspec/test-reports tests/core/pyspec/test-reports

View File

@ -1,9 +1,10 @@
SPEC_DIR = ./specs SPEC_DIR = ./specs
SSZ_DIR = ./ssz
SCRIPT_DIR = ./scripts SCRIPT_DIR = ./scripts
TEST_LIBS_DIR = ./test_libs TEST_LIBS_DIR = ./tests/core
PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec
TEST_VECTOR_DIR = ./eth2.0-spec-tests/tests TEST_VECTOR_DIR = ./eth2.0-spec-tests/tests
GENERATOR_DIR = ./test_generators GENERATOR_DIR = ./tests/generators
DEPOSIT_CONTRACT_DIR = ./deposit_contract DEPOSIT_CONTRACT_DIR = ./deposit_contract
CONFIGS_DIR = ./configs CONFIGS_DIR = ./configs
@ -16,17 +17,19 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER
# To check generator matching: # To check generator matching:
#$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}]) #$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}])
PHASE0_SPEC_DIR = $(SPEC_DIR)/phase0
PY_SPEC_PHASE_0_TARGETS = $(PY_SPEC_DIR)/eth2spec/phase0/spec.py PY_SPEC_PHASE_0_TARGETS = $(PY_SPEC_DIR)/eth2spec/phase0/spec.py
PY_SPEC_PHASE_0_DEPS = $(wildcard $(SPEC_DIR)/core/0_*.md) PY_SPEC_PHASE_0_DEPS = $(wildcard $(SPEC_DIR)/phase0/*.md)
PHASE1_SPEC_DIR = $(SPEC_DIR)/phase1
PY_SPEC_PHASE_1_TARGETS = $(PY_SPEC_DIR)/eth2spec/phase1/spec.py PY_SPEC_PHASE_1_TARGETS = $(PY_SPEC_DIR)/eth2spec/phase1/spec.py
PY_SPEC_PHASE_1_DEPS = $(wildcard $(SPEC_DIR)/core/1_*.md) PY_SPEC_PHASE_1_DEPS = $(wildcard $(SPEC_DIR)/phase1/*.md)
PY_SPEC_ALL_DEPS = $(PY_SPEC_PHASE_0_DEPS) $(PY_SPEC_PHASE_1_DEPS) PY_SPEC_ALL_DEPS = $(PY_SPEC_PHASE_0_DEPS) $(PY_SPEC_PHASE_1_DEPS)
PY_SPEC_ALL_TARGETS = $(PY_SPEC_PHASE_0_TARGETS) $(PY_SPEC_PHASE_1_TARGETS) PY_SPEC_ALL_TARGETS = $(PY_SPEC_PHASE_0_TARGETS) $(PY_SPEC_PHASE_1_TARGETS)
MARKDOWN_FILES = $(PY_SPEC_ALL_DEPS) $(wildcard $(SPEC_DIR)/*.md) $(wildcard $(SPEC_DIR)/light_client/*.md) $(wildcard $(SPEC_DIR)/networking/*.md) $(wildcard $(SPEC_DIR)/validator/*.md) MARKDOWN_FILES = $(PY_SPEC_ALL_DEPS) $(wildcard $(SPEC_DIR)/*.md) $(wildcard $(SSZ_DIR)/*.md) $(wildcard $(SPEC_DIR)/networking/*.md) $(wildcard $(SPEC_DIR)/validator/*.md)
COV_HTML_OUT=.htmlcov COV_HTML_OUT=.htmlcov
COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html
@ -91,7 +94,7 @@ install_deposit_contract_test: $(PY_SPEC_ALL_TARGETS)
compile_deposit_contract: compile_deposit_contract:
cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \ cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \
python tool/compile_deposit_contract.py contracts/validator_registration.v.py; python tool/compile_deposit_contract.py contracts/validator_registration.vy;
test_deposit_contract: test_deposit_contract:
cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \ cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \
@ -101,10 +104,10 @@ test_deposit_contract:
pyspec: $(PY_SPEC_ALL_TARGETS) pyspec: $(PY_SPEC_ALL_TARGETS)
$(PY_SPEC_PHASE_0_TARGETS): $(PY_SPEC_PHASE_0_DEPS) $(PY_SPEC_PHASE_0_TARGETS): $(PY_SPEC_PHASE_0_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p0 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/0_fork-choice.md $(SPEC_DIR)/validator/0_beacon-chain-validator.md $@ python3 $(SCRIPT_DIR)/build_spec.py -p0 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(PHASE0_SPEC_DIR)/validator.md $@
$(PY_SPEC_DIR)/eth2spec/phase1/spec.py: $(PY_SPEC_PHASE_1_DEPS) $(PY_SPEC_DIR)/eth2spec/phase1/spec.py: $(PY_SPEC_PHASE_1_DEPS)
python3 $(SCRIPT_DIR)/build_spec.py -p1 $(SPEC_DIR)/core/0_beacon-chain.md $(SPEC_DIR)/core/0_fork-choice.md $(SPEC_DIR)/light_client/merkle_proofs.md $(SPEC_DIR)/core/1_custody-game.md $(SPEC_DIR)/core/1_shard-data-chains.md $(SPEC_DIR)/core/1_beacon-chain-misc.md $@ python3 $(SCRIPT_DIR)/build_spec.py -p1 $(PHASE0_SPEC_DIR)/beacon-chain.md $(PHASE0_SPEC_DIR)/fork-choice.md $(SSZ_DIR)/merkle-proofs.md $(PHASE1_SPEC_DIR)/custody-game.md $(PHASE1_SPEC_DIR)/shard-data-chains.md $(PHASE1_SPEC_DIR)/beacon-chain-misc.md $@
CURRENT_DIR = ${CURDIR} CURRENT_DIR = ${CURDIR}

View File

@ -9,18 +9,19 @@ This repository hosts the current Eth2 specifications. Discussions about design
## Specs ## Specs
Core specifications for Eth2 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: Core specifications for Eth2 clients be found in [specs/](specs/). These are divided into phases. Each subsequent phase depends upon the prior. The current phases specified are:
### Phase 0 ### Phase 0
* [The Beacon Chain](specs/core/0_beacon-chain.md) * [The Beacon Chain](specs/phase0/beacon-chain.md)
* [Fork Choice](specs/core/0_fork-choice.md) * [Fork Choice](specs/phase0/fork-choice.md)
* [Deposit Contract](specs/core/0_deposit-contract.md) * [Deposit Contract](specs/phase0/deposit-contract.md)
* [Honest Validator](specs/validator/0_beacon-chain-validator.md) * [Honest Validator](specs/phase0/validator.md)
### Phase 1 ### Phase 1
* [Custody Game](specs/core/1_custody-game.md) * [Custody Game](specs/phase1/custody-game.md)
* [Shard Data Chains](specs/core/1_shard-data-chains.md) * [Shard Data Chains](specs/phase1/shard-data-chains.md)
* [Misc beacon chain updates](specs/core/1_beacon-chain-misc.md) * [Misc beacon chain updates](specs/phase1/beacon-chain-misc.md)
* [Light client syncing protocol](specs/phase1/light-client-sync.md)
### Phase 2 ### Phase 2
@ -30,11 +31,9 @@ See the [Eth2 Phase 2 Wiki](https://hackmd.io/UzysWse1Th240HELswKqVA?view) for c
### 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) * [SimpleSerialize (SSZ) spec](ssz/simple-serialize.md)
* [BLS signature verification](specs/bls_signature.md) * [Merkle proof formats](ssz/merkle-proofs.md)
* [General test format](specs/test_formats/README.md) * [General test format](tests/formats/README.md)
* [Merkle proof formats](specs/light_client/merkle_proofs.md)
* [Light client syncing protocol](specs/light_client/sync_protocol.md)
## Additional specifications for client implementers ## Additional specifications for client implementers
@ -63,6 +62,6 @@ The following are the broad design goals for Ethereum 2.0:
## For spec contributors ## For spec contributors
Documentation on the different components used during spec writing can be found here: Documentation on the different components used during spec writing can be found here:
* [YAML Test Generators](test_generators/README.md) * [YAML Test Generators](tests/generators/README.md)
* [Executable Python Spec, with Py-tests](test_libs/pyspec/README.md) * [Executable Python Spec, with Py-tests](tests/core/pyspec/README.md)

View File

@ -39,6 +39,8 @@ TARGET_AGGREGATORS_PER_COMMITTEE: 16
RANDOM_SUBNETS_PER_VALIDATOR: 1 RANDOM_SUBNETS_PER_VALIDATOR: 1
# 2**8 (= 256) # 2**8 (= 256)
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256 EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# Deposit contract # Deposit contract
@ -61,13 +63,15 @@ EFFECTIVE_BALANCE_INCREMENT: 1000000000
# Initial values # Initial values
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 0, GENESIS_EPOCH is derived from this constant # Mainnet initial fork version, recommend altering for testnets
GENESIS_SLOT: 0 GENESIS_FORK_VERSION: 0x00000000
BLS_WITHDRAWAL_PREFIX: 0x00 BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters # Time parameters
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 86400 seconds (1 day)
MIN_GENESIS_DELAY: 86400
# 12 seconds # 12 seconds
SECONDS_PER_SLOT: 12 SECONDS_PER_SLOT: 12
# 2**0 (= 1) slots 12 seconds # 2**0 (= 1) slots 12 seconds

View File

@ -39,6 +39,8 @@ TARGET_AGGREGATORS_PER_COMMITTEE: 16
RANDOM_SUBNETS_PER_VALIDATOR: 1 RANDOM_SUBNETS_PER_VALIDATOR: 1
# 2**8 (= 256) # 2**8 (= 256)
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256 EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# Deposit contract # Deposit contract
@ -61,13 +63,15 @@ EFFECTIVE_BALANCE_INCREMENT: 1000000000
# Initial values # Initial values
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 0, GENESIS_EPOCH is derived from this constant # Highest byte set to 0x01 to avoid collisions with mainnet versioning
GENESIS_SLOT: 0 GENESIS_FORK_VERSION: 0x00000001
BLS_WITHDRAWAL_PREFIX: 0x00 BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters # Time parameters
# --------------------------------------------------------------- # ---------------------------------------------------------------
# [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis
MIN_GENESIS_DELAY: 300
# [customized] Faster for testing purposes # [customized] Faster for testing purposes
SECONDS_PER_SLOT: 6 SECONDS_PER_SLOT: 6
# 2**0 (= 1) slots 6 seconds # 2**0 (= 1) slots 6 seconds

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
# Vyper target 0.1.0b13 # Vyper target 0.1.0b13.hotfix1761
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32 DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32
MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1 MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1

View File

@ -1,5 +1,5 @@
eth-tester[py-evm]==0.1.0b39 eth-tester[py-evm]==0.1.0b39
vyper==0.1.0b13 git+https://github.com/vyperlang/vyper@1761-HOTFIX-v0.1.0-beta.13
web3==5.0.0b2 web3==5.0.0b2
pytest==3.6.1 pytest==3.6.1
../test_libs/pyspec ../tests/core/pyspec

View File

@ -5,7 +5,7 @@ DIR = os.path.dirname(__file__)
def get_deposit_contract_code(): def get_deposit_contract_code():
file_path = os.path.join(DIR, './../../contracts/validator_registration.v.py') file_path = os.path.join(DIR, './../../contracts/validator_registration.vy')
deposit_contract_code = open(file_path).read() deposit_contract_code = open(file_path).read()
return deposit_contract_code return deposit_contract_code

View File

@ -11,7 +11,7 @@ from typing import (
PHASE0_IMPORTS = '''from typing import ( PHASE0_IMPORTS = '''from typing import (
Any, Dict, Set, Sequence, Tuple, Optional Any, Dict, Set, Sequence, Tuple, Optional, TypeVar
) )
from dataclasses import ( from dataclasses import (
@ -21,20 +21,17 @@ from dataclasses import (
from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
boolean, Container, List, Vector, uint64, boolean, Container, List, Vector, uint64, SSZType,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector,
) )
from eth2spec.utils.bls import ( from eth2spec.utils import bls
bls_aggregate_signatures,
bls_aggregate_pubkeys,
bls_verify,
bls_sign,
)
from eth2spec.utils.hash_function import hash from eth2spec.utils.hash_function import hash
SSZObject = TypeVar('SSZObject', bound=SSZType)
''' '''
PHASE1_IMPORTS = '''from typing import ( PHASE1_IMPORTS = '''from typing import (
Any, Dict, Set, Sequence, MutableSequence, NewType, Tuple, Union, Any, Dict, Set, Sequence, MutableSequence, NewType, Tuple, Union, TypeVar
) )
from math import ( from math import (
log2, log2,
@ -51,22 +48,18 @@ from eth2spec.utils.ssz.ssz_impl import (
) )
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
BasicValue, Elements, BaseBytes, BaseList, SSZType, BasicValue, Elements, BaseBytes, BaseList, SSZType,
Container, List, Vector, Bytes, BytesN, Bitlist, Bitvector, Bits, Container, List, Vector, ByteList, ByteVector, Bitlist, Bitvector, Bits,
Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96,
uint64, bit, boolean, byte, uint64, bit, boolean, byte,
) )
from eth2spec.utils.bls import ( from eth2spec.utils import bls
bls_aggregate_pubkeys,
bls_verify,
bls_verify_multiple,
bls_signature_to_G2,
)
from eth2spec.utils.hash_function import hash from eth2spec.utils.hash_function import hash
SSZVariableName = str SSZVariableName = str
GeneralizedIndex = NewType('GeneralizedIndex', int) GeneralizedIndex = NewType('GeneralizedIndex', int)
SSZObject = TypeVar('SSZObject', bound=SSZType)
''' '''
SUNDRY_CONSTANTS_FUNCTIONS = ''' SUNDRY_CONSTANTS_FUNCTIONS = '''
def ceillog2(x: uint64) -> int: def ceillog2(x: uint64) -> int:
@ -163,8 +156,6 @@ def objects_to_spec(functions: Dict[str, str],
del functions[k] del functions[k]
functions_spec = '\n\n'.join(functions.values()) functions_spec = '\n\n'.join(functions.values())
for k in list(constants.keys()): for k in list(constants.keys()):
if k.startswith('DOMAIN_'):
constants[k] = f"DomainType(({constants[k]}).to_bytes(length=4, byteorder='little'))"
if k == "BLS12_381_Q": if k == "BLS12_381_Q":
constants[k] += " # noqa: E501" constants[k] += " # noqa: E501"
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants)) constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants))
@ -206,10 +197,10 @@ def combine_constants(old_constants: Dict[str, str], new_constants: Dict[str, st
ignored_dependencies = [ ignored_dependencies = [
'bit', 'boolean', 'Vector', 'List', 'Container', 'Root', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN' 'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'ByteList', 'ByteVector'
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', 'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
'bytes', 'byte', 'BytesN' # to be removed after updating spec doc 'bytes', 'byte', 'ByteVector' # to be removed after updating spec doc
] ]
@ -317,18 +308,18 @@ if __name__ == '__main__':
description = ''' description = '''
Build the specs from the md docs. Build the specs from the md docs.
If building phase 0: If building phase 0:
1st argument is input /core/0_beacon-chain.md 1st argument is input phase0/beacon-chain.md
2nd argument is input /core/0_fork-choice.md 2nd argument is input phase0/fork-choice.md
3rd argument is input /core/0_beacon-chain-validator.md 3rd argument is input phase0/validator.md
4th argument is output spec.py 4th argument is output spec.py
If building phase 1: If building phase 1:
1st argument is input /core/0_beacon-chain.md 1st argument is input phase0/beacon-chain.md
2nd argument is input /core/0_fork-choice.md 2nd argument is input phase0/fork-choice.md
3rd argument is input /light_client/merkle_proofs.md 3rd argument is input ssz/merkle-proofs.md
4th argument is input /core/1_custody-game.md 4th argument is input phase1/custody-game.md
5th argument is input /core/1_shard-data-chains.md 5th argument is input phase1/shard-data-chains.md
6th argument is input /core/1_beacon-chain-misc.md 6th argument is input phase1/beacon-chain-misc.md
7th argument is output spec.py 7th argument is output spec.py
''' '''
parser = ArgumentParser(description=description) parser = ArgumentParser(description=description)
@ -347,9 +338,9 @@ If building phase 1:
else: else:
print( print(
" Phase 1 requires input files as well as an output file:\n" " Phase 1 requires input files as well as an output file:\n"
"\t core/phase_0: (0_beacon-chain.md, 0_fork-choice.md)\n" "\t phase0: (beacon-chain.md, fork-choice.md)\n"
"\t light_client: (merkle_proofs.md)\n" "\t ssz: (merkle-proofs.md)\n"
"\t core/phase_1: (1_custody-game.md, 1_shard-data-chains.md, 1_beacon-chain-misc.md)\n" "\t phase1: (custody-game.md, shard-data-chains.md, beacon-chain-misc.md)\n"
"\t and output.py" "\t and output.py"
) )
else: else:

View File

@ -1,148 +0,0 @@
# BLS signature verification
**Notice**: This document is a placeholder to facilitate the emergence of cross-client testnets. Substantive changes are postponed until [BLS standardisation](https://github.com/pairingwg/bls_standard) is finalized.
**Warning**: The constructions in this document should not be considered secure. In particular, the `hash_to_G2` function is known to be unsecure.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Curve parameters](#curve-parameters)
- [Point representations](#point-representations)
- [G1 points](#g1-points)
- [G2 points](#g2-points)
- [Helpers](#helpers)
- [`hash_to_G2`](#hash_to_g2)
- [`modular_squareroot`](#modular_squareroot)
- [Aggregation operations](#aggregation-operations)
- [`bls_aggregate_pubkeys`](#bls_aggregate_pubkeys)
- [`bls_aggregate_signatures`](#bls_aggregate_signatures)
- [Signature verification](#signature-verification)
- [`bls_verify`](#bls_verify)
- [`bls_verify_multiple`](#bls_verify_multiple)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Curve parameters
The BLS12-381 curve parameters are defined [here](https://z.cash/blog/new-snark-curve).
## Point representations
We represent points in the groups G1 and G2 following [zkcrypto/pairing](https://github.com/zkcrypto/pairing/tree/master/src/bls12_381). We denote by `q` the field modulus and by `i` the imaginary unit.
### G1 points
A point in G1 is represented as a 384-bit integer `z` decomposed as a 381-bit integer `x` and three 1-bit flags in the top bits:
* `x = z % 2**381`
* `a_flag = (z % 2**382) // 2**381`
* `b_flag = (z % 2**383) // 2**382`
* `c_flag = (z % 2**384) // 2**383`
Respecting bit ordering, `z` is decomposed as `(c_flag, b_flag, a_flag, x)`.
We require:
* `x < q`
* `c_flag == 1`
* if `b_flag == 1` then `a_flag == x == 0` and `z` represents the point at infinity
* if `b_flag == 0` then `z` represents the point `(x, y)` where `y` is the valid coordinate such that `(y * 2) // q == a_flag`
### G2 points
A point in G2 is represented as a pair of 384-bit integers `(z1, z2)`. We decompose `z1` as above into `x1`, `a_flag1`, `b_flag1`, `c_flag1` and `z2` into `x2`, `a_flag2`, `b_flag2`, `c_flag2`.
We require:
* `x1 < q` and `x2 < q`
* `a_flag2 == b_flag2 == c_flag2 == 0`
* `c_flag1 == 1`
* if `b_flag1 == 1` then `a_flag1 == x1 == x2 == 0` and `(z1, z2)` represents the point at infinity
* if `b_flag1 == 0` then `(z1, z2)` represents the point `(x1 * i + x2, y)` where `y` is the valid coordinate such that the imaginary part `y_im` of `y` satisfies `(y_im * 2) // q == a_flag1`
## Helpers
### `hash_to_G2`
```python
G2_cofactor = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109
q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
def hash_to_G2(message_hash: Bytes32, domain: Bytes8) -> Tuple[uint384, uint384]:
# Initial candidate x coordinate
x_re = int.from_bytes(hash(message_hash + domain + b'\x01'), 'big')
x_im = int.from_bytes(hash(message_hash + domain + b'\x02'), 'big')
x_coordinate = Fq2([x_re, x_im]) # x = x_re + i * x_im
# Test candidate y coordinates until a one is found
while 1:
y_coordinate_squared = x_coordinate ** 3 + Fq2([4, 4]) # The curve is y^2 = x^3 + 4(i + 1)
y_coordinate = modular_squareroot(y_coordinate_squared)
if y_coordinate is not None: # Check if quadratic residue found
return multiply_in_G2((x_coordinate, y_coordinate), G2_cofactor)
x_coordinate += Fq2([1, 0]) # Add 1 and try again
```
### `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).
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
eighth_roots_of_unity = [Fq2([1,1]) ** ((Fq2_order * k) // 8) for k in range(8)]
def modular_squareroot(value: Fq2) -> Fq2:
candidate_squareroot = value ** ((Fq2_order + 8) // 16)
check = candidate_squareroot ** 2 / value
if check in eighth_roots_of_unity[::2]:
x1 = candidate_squareroot / eighth_roots_of_unity[eighth_roots_of_unity.index(check) // 2]
x2 = -x1
x1_re, x1_im = coerce_to_int(x1.coeffs[0]), coerce_to_int(x1.coeffs[1])
x2_re, x2_im = coerce_to_int(x2.coeffs[0]), coerce_to_int(x2.coeffs[1])
return x1 if (x1_im > x2_im or (x1_im == x2_im and x1_re > x2_re)) else x2
return None
```
## Aggregation operations
### `bls_aggregate_pubkeys`
Let `bls_aggregate_pubkeys(pubkeys: List[Bytes48]) -> Bytes48` return `pubkeys[0] + .... + pubkeys[len(pubkeys)-1]`, where `+` is the elliptic curve addition operation over the G1 curve. (When `len(pubkeys) == 0` the empty sum is the G1 point at infinity.)
### `bls_aggregate_signatures`
Let `bls_aggregate_signatures(signatures: List[Bytes96]) -> Bytes96` return `signatures[0] + .... + signatures[len(signatures)-1]`, where `+` is the elliptic curve addition operation over the G2 curve. (When `len(signatures) == 0` the empty sum is the G2 point at infinity.)
## Signature verification
In the following, `e` is the pairing function and `g` is the G1 generator with the following coordinates (see [here](https://github.com/zkcrypto/pairing/tree/master/src/bls12_381#g1)):
```python
g_x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
g_y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
g = Fq2([g_x, g_y])
```
### `bls_verify`
Let `bls_verify(pubkey: Bytes48, message_hash: Bytes32, signature: Bytes96, domain: Bytes8) -> bool`:
* Verify that `pubkey` is a valid G1 point.
* Verify that `signature` is a valid G2 point.
* Verify that `e(pubkey, hash_to_G2(message_hash, domain)) == e(g, signature)`.
### `bls_verify_multiple`
Let `bls_verify_multiple(pubkeys: List[Bytes48], message_hashes: List[Bytes32], signature: Bytes96, domain: Bytes8) -> bool`:
* Verify that each `pubkey` in `pubkeys` is a valid G1 point.
* Verify that `signature` is a valid G2 point.
* Verify that `len(pubkeys)` equals `len(message_hashes)` and denote the length `L`.
* Verify that `e(pubkeys[0], hash_to_G2(message_hashes[0], domain)) * ... * e(pubkeys[L-1], hash_to_G2(message_hashes[L-1], domain)) == e(g, signature)`.

View File

@ -34,6 +34,7 @@
- [`DepositMessage`](#depositmessage) - [`DepositMessage`](#depositmessage)
- [`DepositData`](#depositdata) - [`DepositData`](#depositdata)
- [`BeaconBlockHeader`](#beaconblockheader) - [`BeaconBlockHeader`](#beaconblockheader)
- [`SigningRoot`](#signingroot)
- [Beacon operations](#beacon-operations) - [Beacon operations](#beacon-operations)
- [`ProposerSlashing`](#proposerslashing) - [`ProposerSlashing`](#proposerslashing)
- [`AttesterSlashing`](#attesterslashing) - [`AttesterSlashing`](#attesterslashing)
@ -58,8 +59,7 @@
- [Crypto](#crypto) - [Crypto](#crypto)
- [`hash`](#hash) - [`hash`](#hash)
- [`hash_tree_root`](#hash_tree_root) - [`hash_tree_root`](#hash_tree_root)
- [`bls_verify`](#bls_verify) - [BLS Signatures](#bls-signatures)
- [`bls_aggregate_pubkeys`](#bls_aggregate_pubkeys)
- [Predicates](#predicates) - [Predicates](#predicates)
- [`is_active_validator`](#is_active_validator) - [`is_active_validator`](#is_active_validator)
- [`is_eligible_for_activation_queue`](#is_eligible_for_activation_queue) - [`is_eligible_for_activation_queue`](#is_eligible_for_activation_queue)
@ -76,6 +76,7 @@
- [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch) - [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch)
- [`compute_activation_exit_epoch`](#compute_activation_exit_epoch) - [`compute_activation_exit_epoch`](#compute_activation_exit_epoch)
- [`compute_domain`](#compute_domain) - [`compute_domain`](#compute_domain)
- [`compute_signing_root`](#compute_signing_root)
- [Beacon state accessors](#beacon-state-accessors) - [Beacon state accessors](#beacon-state-accessors)
- [`get_current_epoch`](#get_current_epoch) - [`get_current_epoch`](#get_current_epoch)
- [`get_previous_epoch`](#get_previous_epoch) - [`get_previous_epoch`](#get_previous_epoch)
@ -158,10 +159,11 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value | | Name | Value |
| - | - | | - | - |
| `GENESIS_SLOT` | `Slot(0)` |
| `GENESIS_EPOCH` | `Epoch(0)` |
| `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` | | `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` |
| `BASE_REWARDS_PER_EPOCH` | `4` | | `BASE_REWARDS_PER_EPOCH` | `4` |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
| `SECONDS_PER_DAY` | `86400` |
| `JUSTIFICATION_BITS_LENGTH` | `4` | | `JUSTIFICATION_BITS_LENGTH` | `4` |
| `ENDIANNESS` | `'little'` | | `ENDIANNESS` | `'little'` |
@ -182,7 +184,7 @@ The following values are (non-configurable) constants used throughout the specif
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**14` (= 16,384) | | `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `2**14` (= 16,384) |
| `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) | | `MIN_GENESIS_TIME` | `1578009600` (Jan 3, 2020) |
- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) - For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
### Gwei values ### Gwei values
@ -197,24 +199,24 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value | | Name | Value |
| - | - | | - | - |
| `GENESIS_SLOT` | `Slot(0)` | | `GENESIS_FORK_VERSION` | `Version('0x00000000')` |
| `GENESIS_EPOCH` | `Epoch(0)` | | `BLS_WITHDRAWAL_PREFIX` | `Bytes1('0x00')` |
| `BLS_WITHDRAWAL_PREFIX` | `Bytes1(b'\x00')` |
### Time parameters ### Time parameters
| Name | Value | Unit | Duration | | Name | Value | Unit | Duration |
| - | - | :-: | :-: | | - | - | :-: | :-: |
| `MIN_GENESIS_DELAY` | `86400` | seconds | 1 day |
| `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds | | `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds |
| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds |
| `SLOTS_PER_EPOCH` | `2**5` (= 32) | slots | 6.4 minutes | | `SLOTS_PER_EPOCH` | `2**5` (= 32) | slots | 6.4 minutes |
| `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes | | `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes |
| `MAX_SEED_LOOKAHEAD` | `2**2` (= 4) | epochs | 25.6 minutes | | `MAX_SEED_LOOKAHEAD` | `2**2` (= 4) | epochs | 25.6 minutes |
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes |
| `SLOTS_PER_ETH1_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~3.4 hours | | `SLOTS_PER_ETH1_VOTING_PERIOD` | `2**10` (= 1,024) | slots | ~3.4 hours |
| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours | | `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~27 hours |
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours |
| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | | `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days |
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `2**2` (= 4) | epochs | 25.6 minutes |
### State list lengths ### State list lengths
@ -223,7 +225,7 @@ The following values are (non-configurable) constants used throughout the specif
| `EPOCHS_PER_HISTORICAL_VECTOR` | `2**16` (= 65,536) | epochs | ~0.8 years | | `EPOCHS_PER_HISTORICAL_VECTOR` | `2**16` (= 65,536) | epochs | ~0.8 years |
| `EPOCHS_PER_SLASHINGS_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days | | `EPOCHS_PER_SLASHINGS_VECTOR` | `2**13` (= 8,192) | epochs | ~36 days |
| `HISTORICAL_ROOTS_LIMIT` | `2**24` (= 16,777,216) | historical roots | ~26,131 years | | `HISTORICAL_ROOTS_LIMIT` | `2**24` (= 16,777,216) | historical roots | ~26,131 years |
| `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validator spots | | `VALIDATOR_REGISTRY_LIMIT` | `2**40` (= 1,099,511,627,776) | validators |
### Rewards and penalties ### Rewards and penalties
@ -249,19 +251,17 @@ The following values are (non-configurable) constants used throughout the specif
### Domain types ### Domain types
The following types are defined, mapping into `DomainType` (little endian):
| Name | Value | | Name | Value |
| - | - | | - | - |
| `DOMAIN_BEACON_PROPOSER` | `0` | | `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` |
| `DOMAIN_BEACON_ATTESTER` | `1` | | `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` |
| `DOMAIN_RANDAO` | `2` | | `DOMAIN_RANDAO` | `DomainType('0x02000000')` |
| `DOMAIN_DEPOSIT` | `3` | | `DOMAIN_DEPOSIT` | `DomainType('0x03000000')` |
| `DOMAIN_VOLUNTARY_EXIT` | `4` | | `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` |
## Containers ## Containers
The following types are [SimpleSerialize (SSZ)](../simple-serialize.md) containers. The following types are [SimpleSerialize (SSZ)](../../ssz/simple-serialize.md) containers.
*Note*: The definitions are ordered topologically to facilitate execution of the spec. *Note*: The definitions are ordered topologically to facilitate execution of the spec.
@ -366,7 +366,7 @@ class DepositData(Container):
pubkey: BLSPubkey pubkey: BLSPubkey
withdrawal_credentials: Bytes32 withdrawal_credentials: Bytes32
amount: Gwei amount: Gwei
signature: BLSSignature # signing over DepositMessage signature: BLSSignature # Signing over DepositMessage
``` ```
#### `BeaconBlockHeader` #### `BeaconBlockHeader`
@ -379,6 +379,14 @@ class BeaconBlockHeader(Container):
body_root: Root body_root: Root
``` ```
#### `SigningRoot`
```python
class SigningRoot(Container):
object_root: Root
domain: Domain
```
### Beacon operations ### Beacon operations
#### `ProposerSlashing` #### `ProposerSlashing`
@ -411,7 +419,7 @@ class Attestation(Container):
```python ```python
class Deposit(Container): class Deposit(Container):
proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit data list root proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root
data: DepositData data: DepositData
``` ```
@ -488,8 +496,6 @@ class BeaconState(Container):
### Signed envelopes ### Signed envelopes
Some messages in the protocol are wrapped in an envelope to better facilitate adding/pruning the signature and to `hash_tree_root` the `message` separate from the signature.
#### `SignedVoluntaryExit` #### `SignedVoluntaryExit`
```python ```python
@ -573,15 +579,19 @@ def bytes_to_int(data: bytes) -> uint64:
#### `hash_tree_root` #### `hash_tree_root`
`def hash_tree_root(object: SSZSerializable) -> Root` is a function for hashing objects into a single root by utilizing a hash tree structure, as defined in the [SSZ spec](../simple-serialize.md#merkleization). `def hash_tree_root(object: SSZSerializable) -> Root` is a function for hashing objects into a single root by utilizing a hash tree structure, as defined in the [SSZ spec](../../ssz/simple-serialize.md#merkleization).
#### `bls_verify` #### BLS Signatures
`bls_verify` is a function for verifying a BLS signature, as defined in the [BLS Signature spec](../bls_signature.md#bls_verify). Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00). Specifically, eth2 uses the `BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_POP_` ciphersuite which implements the following interfaces:
#### `bls_aggregate_pubkeys` - `def Sign(SK: int, message: Bytes) -> BLSSignature`
- `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
- `def Aggregate(signatures: Sequence[BLSSignature]) -> BLSSignature`
- `def FastAggregateVerify(PKs: Sequence[BLSSignature], message: Bytes, signature: BLSSignature) -> bool`
- `def AggregateVerify(pairs: Sequence[PK: BLSSignature, message: Bytes], signature: BLSSignature) -> bool`
`bls_aggregate_pubkeys` is a function for aggregating multiple BLS public keys into a single aggregate key, as defined in the [BLS Signature spec](../bls_signature.md#bls_aggregate_pubkeys). Within these specifications, BLS signatures are treated as a module for notational clarity, thus to verify a signature `bls.Verify(...)` is used.
### Predicates ### Predicates
@ -664,14 +674,10 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
if not indices == sorted(set(indices)): if not indices == sorted(set(indices)):
return False return False
# Verify aggregate signature # Verify aggregate signature
if not bls_verify( pubkeys = [state.validators[i].pubkey for i in indices]
pubkey=bls_aggregate_pubkeys([state.validators[i].pubkey for i in indices]), domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
message_hash=hash_tree_root(indexed_attestation.data), signing_root = compute_signing_root(indexed_attestation.data, domain)
signature=indexed_attestation.signature, return bls.FastAggregateVerify(pubkeys, signing_root, indexed_attestation.signature)
domain=get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch),
):
return False
return True
``` ```
#### `is_valid_merkle_branch` #### `is_valid_merkle_branch`
@ -782,13 +788,27 @@ def compute_activation_exit_epoch(epoch: Epoch) -> Epoch:
#### `compute_domain` #### `compute_domain`
```python ```python
def compute_domain(domain_type: DomainType, fork_version: Version=Version()) -> Domain: def compute_domain(domain_type: DomainType, fork_version: Version=GENESIS_FORK_VERSION) -> Domain:
""" """
Return the domain for the ``domain_type`` and ``fork_version``. Return the domain for the ``domain_type`` and ``fork_version``.
""" """
return Domain(domain_type + fork_version) return Domain(domain_type + fork_version)
``` ```
### `compute_signing_root`
```python
def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root:
"""
Return the signing root of an object by calculating the root of the object-domain tree.
"""
domain_wrapped_object = SigningRoot(
object_root=hash_tree_root(ssz_object),
domain=domain,
)
return hash_tree_root(domain_wrapped_object)
```
### Beacon state accessors ### Beacon state accessors
#### `get_current_epoch` #### `get_current_epoch`
@ -942,11 +962,11 @@ def get_total_active_balance(state: BeaconState) -> Gwei:
#### `get_domain` #### `get_domain`
```python ```python
def get_domain(state: BeaconState, domain_type: DomainType, message_epoch: Epoch=None) -> Domain: def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain:
""" """
Return the signature domain (fork version concatenated with domain type) of a message. Return the signature domain (fork version concatenated with domain type) of a message.
""" """
epoch = get_current_epoch(state) if message_epoch is None else message_epoch epoch = get_current_epoch(state) if epoch is None else epoch
fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
return compute_domain(domain_type, fork_version) return compute_domain(domain_type, fork_version)
``` ```
@ -1016,7 +1036,7 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
# Compute exit queue epoch # Compute exit queue epoch
exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH]
exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) exit_queue_epoch = max(exit_epochs, default=compute_activation_exit_epoch(get_current_epoch(state)))
exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch])
if exit_queue_churn >= get_validator_churn_limit(state): if exit_queue_churn >= get_validator_churn_limit(state):
exit_queue_epoch += Epoch(1) exit_queue_epoch += Epoch(1)
@ -1065,8 +1085,14 @@ Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 b
def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
eth1_timestamp: uint64, eth1_timestamp: uint64,
deposits: Sequence[Deposit]) -> BeaconState: deposits: Sequence[Deposit]) -> BeaconState:
fork = Fork(
previous_version=GENESIS_FORK_VERSION,
current_version=GENESIS_FORK_VERSION,
epoch=GENESIS_EPOCH,
)
state = BeaconState( state = BeaconState(
genesis_time=eth1_timestamp - eth1_timestamp % SECONDS_PER_DAY + 2 * SECONDS_PER_DAY, genesis_time=eth1_timestamp - eth1_timestamp % MIN_GENESIS_DELAY + 2 * MIN_GENESIS_DELAY,
fork=fork,
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)), eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy
@ -1111,19 +1137,21 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
## Beacon chain state transition function ## Beacon chain state transition function
The post-state corresponding to a pre-state `state` and a block `block` is defined as `state_transition(state, block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid.
```python ```python
def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> BeaconState: def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> BeaconState:
block = signed_block.message
# Process slots (including those with no blocks) since block # Process slots (including those with no blocks) since block
process_slots(state, signed_block.message.slot) process_slots(state, block.slot)
# Verify signature # Verify signature
if validate_result: if validate_result:
assert verify_block_signature(state, signed_block) assert verify_block_signature(state, signed_block)
# Process block # Process block
process_block(state, signed_block.message) process_block(state, block)
# Verify state root
if validate_result: if validate_result:
assert signed_block.message.state_root == hash_tree_root(state) # Validate state root assert block.state_root == hash_tree_root(state)
# Return post-state # Return post-state
return state return state
``` ```
@ -1131,8 +1159,8 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida
```python ```python
def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool:
proposer = state.validators[get_beacon_proposer_index(state)] proposer = state.validators[get_beacon_proposer_index(state)]
domain = get_domain(state, DOMAIN_BEACON_PROPOSER) signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER))
return bls_verify(proposer.pubkey, hash_tree_root(signed_block.message), signed_block.signature, domain) return bls.Verify(proposer.pubkey, signing_root, signed_block.signature)
``` ```
```python ```python
@ -1299,9 +1327,7 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
proposer_reward = Gwei(get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT) proposer_reward = Gwei(get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT)
rewards[attestation.proposer_index] += proposer_reward rewards[attestation.proposer_index] += proposer_reward
max_attester_reward = get_base_reward(state, index) - proposer_reward max_attester_reward = get_base_reward(state, index) - proposer_reward
rewards[index] += Gwei( rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay)
max_attester_reward // attestation.inclusion_delay
)
# Inactivity penalty # Inactivity penalty
finality_delay = previous_epoch - state.finalized_checkpoint.epoch finality_delay = previous_epoch - state.finalized_checkpoint.epoch
@ -1310,9 +1336,8 @@ def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence
for index in eligible_validator_indices: for index in eligible_validator_indices:
penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index)) penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index))
if index not in matching_target_attesting_indices: if index not in matching_target_attesting_indices:
penalties[index] += Gwei( effective_balance = state.validators[index].effective_balance
state.validators[index].effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT)
)
return rewards, penalties return rewards, penalties
``` ```
@ -1412,13 +1437,14 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
assert block.slot == state.slot assert block.slot == state.slot
# Verify that the parent matches # Verify that the parent matches
assert block.parent_root == hash_tree_root(state.latest_block_header) assert block.parent_root == hash_tree_root(state.latest_block_header)
# Save current block as the new latest block # Cache current block as the new latest block
state.latest_block_header = BeaconBlockHeader( state.latest_block_header = BeaconBlockHeader(
slot=block.slot, slot=block.slot,
parent_root=block.parent_root, parent_root=block.parent_root,
# `state_root` is zeroed and overwritten in the next `process_slot` call state_root=Bytes32(), # Overwritten in the next process_slot call
body_root=hash_tree_root(block.body), body_root=hash_tree_root(block.body),
) )
# Verify proposer is not slashed # Verify proposer is not slashed
proposer = state.validators[get_beacon_proposer_index(state)] proposer = state.validators[get_beacon_proposer_index(state)]
assert not proposer.slashed assert not proposer.slashed
@ -1431,7 +1457,8 @@ def process_randao(state: BeaconState, body: BeaconBlockBody) -> None:
epoch = get_current_epoch(state) epoch = get_current_epoch(state)
# Verify RANDAO reveal # Verify RANDAO reveal
proposer = state.validators[get_beacon_proposer_index(state)] proposer = state.validators[get_beacon_proposer_index(state)]
assert bls_verify(proposer.pubkey, hash_tree_root(epoch), body.randao_reveal, get_domain(state, DOMAIN_RANDAO)) signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO))
assert bls.Verify(proposer.pubkey, signing_root, body.randao_reveal)
# Mix in RANDAO reveal # Mix in RANDAO reveal
mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal)) mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal))
state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR] = mix
@ -1469,17 +1496,18 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
```python ```python
def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None: def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSlashing) -> None:
proposer = state.validators[proposer_slashing.proposer_index] # Verify header slots match
# Verify slots match
assert proposer_slashing.signed_header_1.message.slot == proposer_slashing.signed_header_2.message.slot assert proposer_slashing.signed_header_1.message.slot == proposer_slashing.signed_header_2.message.slot
# But the headers are different # Verify the headers are different
assert proposer_slashing.signed_header_1.message != proposer_slashing.signed_header_2.message assert proposer_slashing.signed_header_1 != proposer_slashing.signed_header_2
# Check proposer is slashable # Verify the proposer is slashable
proposer = state.validators[proposer_slashing.proposer_index]
assert is_slashable_validator(proposer, get_current_epoch(state)) assert is_slashable_validator(proposer, get_current_epoch(state))
# Signatures are valid # Verify signatures
for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2):
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot))
assert bls_verify(proposer.pubkey, hash_tree_root(signed_header.message), signed_header.signature, domain) signing_root = compute_signing_root(signed_header.message, domain)
assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature)
slash_validator(state, proposer_slashing.proposer_index) slash_validator(state, proposer_slashing.proposer_index)
``` ```
@ -1530,7 +1558,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
assert data.source == state.previous_justified_checkpoint assert data.source == state.previous_justified_checkpoint
state.previous_epoch_attestations.append(pending_attestation) state.previous_epoch_attestations.append(pending_attestation)
# Check signature # Verify signature
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
``` ```
@ -1542,7 +1570,7 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
assert is_valid_merkle_branch( assert is_valid_merkle_branch(
leaf=hash_tree_root(deposit.data), leaf=hash_tree_root(deposit.data),
branch=deposit.proof, branch=deposit.proof,
depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the `List` length mix-in depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in
index=state.eth1_deposit_index, index=state.eth1_deposit_index,
root=state.eth1_data.deposit_root, root=state.eth1_data.deposit_root,
) )
@ -1554,15 +1582,15 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None:
amount = deposit.data.amount amount = deposit.data.amount
validator_pubkeys = [v.pubkey for v in state.validators] validator_pubkeys = [v.pubkey for v in state.validators]
if pubkey not in validator_pubkeys: if pubkey not in validator_pubkeys:
# Verify the deposit signature (proof of possession) for new validators. # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
# Note: The deposit contract does not check signatures.
# Note: Deposits are valid across forks, thus the deposit domain is retrieved directly from `compute_domain`.
domain = compute_domain(DOMAIN_DEPOSIT)
deposit_message = DepositMessage( deposit_message = DepositMessage(
pubkey=deposit.data.pubkey, pubkey=deposit.data.pubkey,
withdrawal_credentials=deposit.data.withdrawal_credentials, withdrawal_credentials=deposit.data.withdrawal_credentials,
amount=deposit.data.amount) amount=deposit.data.amount,
if not bls_verify(pubkey, hash_tree_root(deposit_message), deposit.data.signature, domain): )
domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks
signing_root = compute_signing_root(deposit_message, domain)
if not bls.Verify(pubkey, signing_root, deposit.data.signature):
return return
# Add validator and balance entries # Add validator and balance entries
@ -1590,7 +1618,7 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu
validator = state.validators[voluntary_exit.validator_index] validator = state.validators[voluntary_exit.validator_index]
# Verify the validator is active # Verify the validator is active
assert is_active_validator(validator, get_current_epoch(state)) assert is_active_validator(validator, get_current_epoch(state))
# Verify the validator has not yet exited # Verify exit has not been initiated
assert validator.exit_epoch == FAR_FUTURE_EPOCH assert validator.exit_epoch == FAR_FUTURE_EPOCH
# Exits must specify an epoch when they become valid; they are not valid before then # Exits must specify an epoch when they become valid; they are not valid before then
assert get_current_epoch(state) >= voluntary_exit.epoch assert get_current_epoch(state) >= voluntary_exit.epoch
@ -1598,7 +1626,8 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu
assert get_current_epoch(state) >= validator.activation_epoch + PERSISTENT_COMMITTEE_PERIOD assert get_current_epoch(state) >= validator.activation_epoch + PERSISTENT_COMMITTEE_PERIOD
# Verify signature # Verify signature
domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
assert bls_verify(validator.pubkey, hash_tree_root(voluntary_exit), signed_voluntary_exit.signature, domain) signing_root = compute_signing_root(voluntary_exit, domain)
assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
# Initiate exit # Initiate exit
initiate_validator_exit(state, voluntary_exit.validator_index) initiate_validator_exit(state, voluntary_exit.validator_index)
``` ```

View File

@ -40,7 +40,7 @@ The initial deployment phases of Ethereum 2.0 are implemented without consensus
### `deposit` function ### `deposit` function
The deposit contract has a public `deposit` function to make deposits. It takes as arguments `pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32`. The first three arguments populate a [`DepositData`](./0_beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata. The deposit contract has a public `deposit` function to make deposits. It takes as arguments `pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32`. The first three arguments populate a [`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata.
#### Deposit amount #### Deposit amount
@ -61,6 +61,6 @@ Every Ethereum 1.0 deposit emits a `DepositEvent` log for consumption by the bea
## Vyper code ## Vyper code
The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.v.py). The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.vy).
*Note*: To save on gas, the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof. *Note*: To save on gas, the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof.

View File

@ -34,7 +34,7 @@
## Introduction ## Introduction
This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0. It assumes the [beacon chain state transition function spec](./0_beacon-chain.md). This document is the beacon chain fork choice spec, part of Ethereum 2.0 Phase 0. It assumes the [beacon chain state transition function spec](./beacon-chain.md).
## Fork choice ## Fork choice
@ -48,7 +48,7 @@ The head block root associated with a `store` is defined as `get_head(store)`. A
1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time). 1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time).
2) **Honest clocks**: Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other. 2) **Honest clocks**: Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other.
3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](../validator/0_beacon-chain-validator.md) should ensure that `state.latest_eth1_data` of the canonical Ethereum 2.0 chain remains consistent with the canonical Ethereum 1.0 chain. If not, emergency manual intervention will be required. 3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](./validator.md) should ensure that `state.latest_eth1_data` of the canonical Ethereum 2.0 chain remains consistent with the canonical Ethereum 1.0 chain. If not, emergency manual intervention will be required.
4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`. 4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`.
5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost). 5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost).
@ -196,7 +196,7 @@ def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconB
```python ```python
def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]:
""" """
Retrieve a filtered block true from ``store``, only returning branches Retrieve a filtered block tree from ``store``, only returning branches
whose leaf state's justified/finalized info agrees with that in ``store``. whose leaf state's justified/finalized info agrees with that in ``store``.
""" """
base = store.justified_checkpoint.root base = store.justified_checkpoint.root

View File

@ -10,8 +10,7 @@ It consists of four main sections:
4. An analysis of the maturity/state of the libp2p features required by this spec across the languages in which Eth2 clients are being developed. 4. An analysis of the maturity/state of the libp2p features required by this spec across the languages in which Eth2 clients are being developed.
## Table of contents ## Table of contents
<!-- TOC -->
<!-- cmd: doctoc --maxlevel=2 p2p-interface.md -->
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@ -82,6 +81,7 @@ It consists of four main sections:
- [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair) - [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair)
- [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes) - [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes)
- [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id) - [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id)
- [Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?](#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets)
- [Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets?](#why-are-there-attestation_subnet_count-attestation-subnets) - [Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets?](#why-are-there-attestation_subnet_count-attestation-subnets)
- [Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots?](#why-are-attestations-limited-to-be-broadcast-on-gossip-channels-within-slots_per_epoch-slots) - [Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots?](#why-are-attestations-limited-to-be-broadcast-on-gossip-channels-within-slots_per_epoch-slots)
- [Why are aggregate attestations broadcast to the global topic as `AggregateAndProof`s rather than just as `Attestation`s?](#why-are-aggregate-attestations-broadcast-to-the-global-topic-as-aggregateandproofs-rather-than-just-as-attestations) - [Why are aggregate attestations broadcast to the global topic as `AggregateAndProof`s rather than just as `Attestation`s?](#why-are-aggregate-attestations-broadcast-to-the-global-topic-as-aggregateandproofs-rather-than-just-as-attestations)
@ -93,6 +93,7 @@ It consists of four main sections:
- [Why are messages length-prefixed with a protobuf varint in the SSZ-encoding?](#why-are-messages-length-prefixed-with-a-protobuf-varint-in-the-ssz-encoding) - [Why are messages length-prefixed with a protobuf varint in the SSZ-encoding?](#why-are-messages-length-prefixed-with-a-protobuf-varint-in-the-ssz-encoding)
- [Why do we version protocol strings with ordinals instead of semver?](#why-do-we-version-protocol-strings-with-ordinals-instead-of-semver) - [Why do we version protocol strings with ordinals instead of semver?](#why-do-we-version-protocol-strings-with-ordinals-instead-of-semver)
- [Why is it called Req/Resp and not RPC?](#why-is-it-called-reqresp-and-not-rpc) - [Why is it called Req/Resp and not RPC?](#why-is-it-called-reqresp-and-not-rpc)
- [Why do we allow empty responses in block requests?](#why-do-we-allow-empty-responses-in-block-requests)
- [Discovery](#discovery) - [Discovery](#discovery)
- [Why are we using discv5 and not libp2p Kademlia DHT?](#why-are-we-using-discv5-and-not-libp2p-kademlia-dht) - [Why are we using discv5 and not libp2p Kademlia DHT?](#why-are-we-using-discv5-and-not-libp2p-kademlia-dht)
- [What is the difference between an ENR and a multiaddr, and why are we using ENRs?](#what-is-the-difference-between-an-enr-and-a-multiaddr-and-why-are-we-using-enrs) - [What is the difference between an ENR and a multiaddr, and why are we using ENRs?](#what-is-the-difference-between-an-enr-and-a-multiaddr-and-why-are-we-using-enrs)
@ -104,6 +105,7 @@ It consists of four main sections:
- [libp2p implementations matrix](#libp2p-implementations-matrix) - [libp2p implementations matrix](#libp2p-implementations-matrix)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
# Network fundamentals # Network fundamentals
@ -185,6 +187,7 @@ This section outlines constants that are used in this spec.
| `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | | `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). |
| `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. |
| `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. | | `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. |
| `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. |
## The gossip domain: gossipsub ## The gossip domain: gossipsub
@ -222,15 +225,15 @@ where `base64` is the [URL-safe base64 alphabet](https://tools.ietf.org/html/rfc
The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic: The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic:
| Topic | Message Type | | Topic | Message Type |
|----------------------------------------|-------------------| |------------------------------------------------|----------------------|
| beacon_block | SignedBeaconBlock | | beacon_block | SignedBeaconBlock |
| beacon_aggregate_and_proof | AggregateAndProof | | beacon_aggregate_and_proof | AggregateAndProof |
| beacon_attestation\* | Attestation | | beacon_attestation\* | Attestation |
| committee_index{subnet_id}\_beacon_attestation | Attestation | | committee_index{subnet_id}\_beacon_attestation | Attestation |
| voluntary_exit | VoluntaryExit | | voluntary_exit | SignedVoluntaryExit |
| proposer_slashing | ProposerSlashing | | proposer_slashing | ProposerSlashing |
| attester_slashing | AttesterSlashing | | attester_slashing | AttesterSlashing |
Clients MUST reject (fail validation) messages containing an incorrect type, or invalid payload. Clients MUST reject (fail validation) messages containing an incorrect type, or invalid payload.
@ -242,11 +245,13 @@ When processing incoming gossip, clients MAY descore or disconnect peers who fai
There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `TopicName`s are: There are two primary global topics used to propagate beacon blocks and aggregate attestations to all nodes on the network. Their `TopicName`s are:
- `beacon_block` - This topic is used solely for propagating new beacon blocks to all nodes on the networks. Blocks are sent in their entirety. Clients MUST validate the block proposer signature before forwarding it across the network. - `beacon_block` - This topic is used solely for propagating new signed beacon blocks to all nodes on the networks. Signed blocks are sent in their entirety. The following validations MUST pass before forwarding the `signed_beacon_block` on the network
- The proposer signature, `signed_beacon_block.signature` is valid.
- The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future blocks for processing at the appropriate slot).
- `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `AggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `aggregate_and_proof` on the network. - `beacon_aggregate_and_proof` - This topic is used to propagate aggregated attestations (as `AggregateAndProof`s) to subscribing nodes (typically validators) to be included in future blocks. The following validations MUST pass before forwarding the `aggregate_and_proof` on the network.
- The aggregate attestation defined by `hash_tree_root(aggregate_and_proof.aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally). - The aggregate attestation defined by `hash_tree_root(aggregate_and_proof.aggregate)` has _not_ already been seen (via aggregate gossip, within a block, or through the creation of an equivalent aggregate locally).
- The block being voted for (`aggregate_and_proof.aggregate.data.beacon_block_root`) passes validation. - The block being voted for (`aggregate_and_proof.aggregate.data.beacon_block_root`) passes validation.
- `aggregate_and_proof.aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (`aggregate_and_proof.aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate_and_proof.aggregate.data.slot`). - `aggregate_and_proof.aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate_and_proof.aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate_and_proof.aggregate.data.slot`.
- The validator index is within the aggregate's committee -- i.e. `aggregate_and_proof.aggregator_index in get_attesting_indices(state, aggregate_and_proof.aggregate.data, aggregate_and_proof.aggregate.aggregation_bits)`. - The validator index is within the aggregate's committee -- i.e. `aggregate_and_proof.aggregator_index in get_attesting_indices(state, aggregate_and_proof.aggregate.data, aggregate_and_proof.aggregate.aggregation_bits)`.
- `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate_and_proof.aggregate.data.slot, aggregate_and_proof.aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. - `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_aggregator(state, aggregate_and_proof.aggregate.data.slot, aggregate_and_proof.aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`.
- The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate_and_proof.aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. - The `aggregate_and_proof.selection_proof` is a valid signature of the `aggregate_and_proof.aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`.
@ -254,7 +259,7 @@ There are two primary global topics used to propagate beacon blocks and aggregat
Additional global topics are used to propagate lower frequency validator messages. Their `TopicName`s are: Additional global topics are used to propagate lower frequency validator messages. Their `TopicName`s are:
- `voluntary_exit` - This topic is used solely for propagating voluntary validator exits to proposers on the network. Voluntary exits are sent in their entirety. Clients who receive a voluntary exit on this topic MUST validate the conditions within `process_voluntary_exit` before forwarding it across the network. - `voluntary_exit` - This topic is used solely for propagating signed voluntary validator exits to proposers on the network. Signed voluntary exits are sent in their entirety. Clients who receive a signed voluntary exit on this topic MUST validate the conditions within `process_voluntary_exit` before forwarding it across the network.
- `proposer_slashing` - This topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. Clients who receive a proposer slashing on this topic MUST validate the conditions within `process_proposer_slashing` before forwarding it across the network. - `proposer_slashing` - This topic is used solely for propagating proposer slashings to proposers on the network. Proposer slashings are sent in their entirety. Clients who receive a proposer slashing on this topic MUST validate the conditions within `process_proposer_slashing` before forwarding it across the network.
- `attester_slashing` - This topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. - `attester_slashing` - This topic is used solely for propagating attester slashings to proposers on the network. Attester slashings are sent in their entirety. Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network.
@ -266,7 +271,7 @@ Attestation subnets are used to propagate unaggregated attestations to subsectio
- The attestation's committee index (`attestation.data.index`) is for the correct subnet. - The attestation's committee index (`attestation.data.index`) is for the correct subnet.
- The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit == 0b1]) == 1`). - The attestation is unaggregated -- that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit == 0b1]) == 1`).
- The block being voted for (`attestation.data.beacon_block_root`) passes validation. - The block being voted for (`attestation.data.beacon_block_root`) passes validation.
- `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (`attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`). - `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`.
- The signature of `attestation` is valid. - The signature of `attestation` is valid.
#### Interop #### Interop
@ -322,14 +327,14 @@ Request/response messages MUST adhere to the encoding specified in the protocol
``` ```
request ::= <encoding-dependent-header> | <encoded-payload> request ::= <encoding-dependent-header> | <encoded-payload>
response ::= <response_chunk>+ response ::= <response_chunk>*
response_chunk ::= <result> | <encoding-dependent-header> | <encoded-payload> response_chunk ::= <result> | <encoding-dependent-header> | <encoded-payload>
result ::= “0” | “1” | “2” | [“128” ... ”255”] result ::= “0” | “1” | “2” | [“128” ... ”255”]
``` ```
The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security.
A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `MAX_CHUNK_SIZE`. A `response` is formed by zero or more `response_chunk`s. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `MAX_CHUNK_SIZE`.
Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `MAX_CHUNK_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `MAX_CHUNK_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance.
@ -352,7 +357,7 @@ The responder MUST:
1. Use the encoding strategy to read the optional header. 1. Use the encoding strategy to read the optional header.
2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure. 2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure.
3. Deserialize the expected type, and process the request. 3. Deserialize the expected type, and process the request.
4. Write the response which may consist of one or more `response_chunk`s (result, optional header, payload). 4. Write the response which may consist of zero or more `response_chunk`s (result, optional header, payload).
5. Close their write side of the stream. At this point, the stream will be fully closed. 5. Close their write side of the stream. At this point, the stream will be fully closed.
If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets.
@ -393,12 +398,12 @@ Here, `result` represents the 1-byte response code.
The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time: The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. Two values are possible at this time:
- `ssz`: the contents are [SSZ-encoded](../simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Bytes32`'s. - `ssz`: the contents are [SSZ-encoded](../../ssz/simple-serialize.md). This encoding type MUST be supported by all clients. For objects containing a single field, only the field is SSZ-encoded not a container with a single field. For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Bytes32`'s.
- `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy). MAY be supported in the interoperability testnet; MUST be supported in mainnet. - `ssz_snappy`: The contents are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy). MAY be supported in the interoperability testnet; MUST be supported in mainnet.
#### SSZ-encoding strategy (with or without Snappy) #### SSZ-encoding strategy (with or without Snappy)
The [SimpleSerialize (SSZ) specification](../simple-serialize.md) outlines how objects are SSZ-encoded. If the Snappy variant is selected, we feed the serialized form to the Snappy compressor on encoding. The inverse happens on decoding. The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded. If the Snappy variant is selected, we feed the serialized form to the Snappy compressor on encoding. The inverse happens on decoding.
**Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST prefix all encoded and compressed (if applicable) payloads with an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). **Encoding-dependent header:** Req/Resp protocols using the `ssz` or `ssz_snappy` encoding strategies MUST prefix all encoded and compressed (if applicable) payloads with an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints).
@ -406,7 +411,7 @@ All messages that contain only a single field MUST be encoded directly as the ty
Responses that are SSZ-lists (for example `[]SignedBeaconBlock`) send their Responses that are SSZ-lists (for example `[]SignedBeaconBlock`) send their
constituents individually as `response_chunk`s. For example, the constituents individually as `response_chunk`s. For example, the
`[]SignedBeaconBlock` response type sends one or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. `[]SignedBeaconBlock` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload.
### Messages ### Messages
@ -496,7 +501,7 @@ Requests count beacon blocks from the peer starting from `start_slot` on the cha
The request MUST be encoded as an SSZ-container. The request MUST be encoded as an SSZ-container.
The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
`BeaconBlocksByRange` is primarily used to sync historical blocks. `BeaconBlocksByRange` is primarily used to sync historical blocks.
@ -504,7 +509,7 @@ Clients MUST support requesting blocks since the start of the weak subjectivity
Clients MUST support `head_block_root` values since the latest finalized epoch. Clients MUST support `head_block_root` values since the latest finalized epoch.
Clients MUST respond with at least one block, if they have it. Clients MUST respond with at least one block, if they have it and it exists in the range. Clients MAY limit the number of blocks in the response.
Clients MUST order blocks by increasing slot number. Clients MUST order blocks by increasing slot number.
@ -534,11 +539,11 @@ Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). T
The request MUST be encoded as an SSZ-field. The request MUST be encoded as an SSZ-field.
The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
Clients MUST support requesting blocks since the latest finalized epoch. Clients MUST support requesting blocks since the latest finalized epoch.
Clients MUST respond with at least one block, if they have it. Clients MUST respond with at least one block, if they have it. Clients MAY limit the number of blocks in the response.
## The discovery domain: discv5 ## The discovery domain: discv5
@ -764,6 +769,14 @@ Some examples of where messages could be duplicated:
* Attestation aggregation strategies where clients partially aggregate attestations and propagate them. Partial aggregates could be duplicated * Attestation aggregation strategies where clients partially aggregate attestations and propagate them. Partial aggregates could be duplicated
* Clients re-publishing seen messages * Clients re-publishing seen messages
### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?
For some gossip channels (e.g. those for Attestations and BeaconBlocks), there are designated ranges of slots during which particular messages can be sent, limiting messages gossiped to those that can be reasonably used in the consensus at the current time/slot. This is to reduce optionality in DoS attacks.
`MAXIMUM_GOSSIP_CLOCK_DISPARITY` provides some leeway in validating slot ranges to prevent the gossip network from becoming overly brittle with respect to clock disparity. For minimum and maximum allowable slot broadcast times, `MAXIMUM_GOSSIP_CLOCK_DISPARITY` MUST be subtracted and added respectively, marginally extending the valid range. Although messages can at times be eagerly gossiped to the network, the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot.
The value of this constant is currently a placeholder and will be tuned based on data observed in testnets.
### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets? ### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets?
Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. The exact grouping will be dependent on more involved network tests. This constant allows for more flexibility in setting up the network topology for attestation aggregation (as aggregation should happen on each subnet). The value is currently set to to be equal `MAX_COMMITTEES_PER_SLOT` until network tests indicate otherwise. Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. The exact grouping will be dependent on more involved network tests. This constant allows for more flexibility in setting up the network topology for attestation aggregation (as aggregation should happen on each subnet). The value is currently set to to be equal `MAX_COMMITTEES_PER_SLOT` until network tests indicate otherwise.
@ -853,6 +866,26 @@ For this reason, we remove and replace semver with ordinals that require explici
Req/Resp is used to avoid confusion with JSON-RPC and similar user-client interaction mechanisms. Req/Resp is used to avoid confusion with JSON-RPC and similar user-client interaction mechanisms.
### Why do we allow empty responses in block requests?
When requesting blocks by range or root, it may happen that there are no blocks in the selected range or the responding node does not have the requested blocks.
Thus, it may happen that we need to transmit an empty list - there are several ways to encode this:
0) Close the stream without sending any data
1) Add a `null` option to the `success` response, for example by introducing an additional byte
2) Respond with an error result, using a specific error code for "No data"
Semantically, it is not an error that a block is missing during a slot making option 2 unnatural.
Option 1 allows allows the responder to signal "no block", but this information may be wrong - for example in the case of a malicious node.
Under option 0, there is no way for a client to distinguish between a slot without a block and an incomplete response, but given that it already must contain logic to handle the uncertainty of a malicious peer, option 0 was chosen. Clients should mark any slots missing blocks as unknown until they can be verified as not containing a block by successive blocks.
Assuming option 0 with no special `null` encoding, consider a request for slots `2, 3, 4` - if there was no block produced at slot 4, the response would be `2, 3, EOF`. Now consider the same situation, but where only `4` is requested - closing the stream with only `EOF` (without any `response_chunk`) is consistent.
Failing to provide blocks that nodes "should" have is reason to trust a peer less - for example, if a particular peer gossips a block, it should have access to its parent. If a request for the parent fails, it's indicative of poor peer quality since peers should validate blocks before gossiping them.
## Discovery ## Discovery
### Why are we using discv5 and not libp2p Kademlia DHT? ### Why are we using discv5 and not libp2p Kademlia DHT?

View File

@ -1,6 +1,6 @@
# Ethereum 2.0 Phase 0 -- Honest Validator # Ethereum 2.0 Phase 0 -- Honest Validator
**Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](../core/0_beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. **Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol.
## Table of contents ## Table of contents
@ -31,6 +31,8 @@
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Randao reveal](#randao-reveal) - [Randao reveal](#randao-reveal)
- [Eth1 Data](#eth1-data) - [Eth1 Data](#eth1-data)
- [`Eth1Block`](#eth1block)
- [`get_eth1_data`](#get_eth1_data)
- [Proposer slashings](#proposer-slashings) - [Proposer slashings](#proposer-slashings)
- [Attester slashings](#attester-slashings) - [Attester slashings](#attester-slashings)
- [Attestations](#attestations) - [Attestations](#attestations)
@ -73,7 +75,7 @@ A validator is an entity that participates in the consensus of the Ethereum 2.0
## Prerequisites ## Prerequisites
All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](../core/0_beacon-chain.md) and [Phase 0 -- Deposit Contract](../core/0_deposit-contract.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout. All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](./beacon-chain.md) and [Phase 0 -- Deposit Contract](./deposit-contract.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout.
## Constants ## Constants
@ -85,6 +87,7 @@ All terminology, constants, functions, and protocol mechanics defined in the [Ph
| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | | | `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | |
| `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | | | `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | |
| `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours | | `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours |
| `SECONDS_PER_ETH1_BLOCK` | `14` | seconds | |
## Becoming a validator ## Becoming a validator
@ -107,17 +110,17 @@ The validator constructs their `withdrawal_credentials` via the following:
### Submit deposit ### Submit deposit
In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 proof-of-work chain. Deposits are made to the [deposit contract](../core/0_deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. In Phase 0, all incoming validator deposits originate from the Ethereum 1.0 proof-of-work chain. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`.
To submit a deposit: To submit a deposit:
- Pack the validator's [initialization parameters](#initialization) into `deposit_data`, a [`DepositData`](../core/0_beacon-chain.md#depositdata) SSZ object. - Pack the validator's [initialization parameters](#initialization) into `deposit_data`, a [`DepositData`](./beacon-chain.md#depositdata) SSZ object.
- Let `amount` be the amount in Gwei to be deposited by the validator where `amount >= MIN_DEPOSIT_AMOUNT`. - Let `amount` be the amount in Gwei to be deposited by the validator where `amount >= MIN_DEPOSIT_AMOUNT`.
- Set `deposit_data.pubkey` to validator's `pubkey`. - Set `deposit_data.pubkey` to validator's `pubkey`.
- Set `deposit_data.withdrawal_credentials` to `withdrawal_credentials`. - Set `deposit_data.withdrawal_credentials` to `withdrawal_credentials`.
- Set `deposit_data.amount` to `amount`. - Set `deposit_data.amount` to `amount`.
- Let `deposit_message` be a `DepositMessage` with all the `DepositData` contents except the `signature`. - Let `deposit_message` be a `DepositMessage` with all the `DepositData` contents except the `signature`.
- Let `signature` be the result of `bls_sign` of the `hash_tree_root(deposit_message)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (Deposits are valid regardless of fork version, `compute_domain` will default to zeroes there). - Let `signature` be the result of `bls.Sign` of the `compute_signing_root(deposit_message, domain)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (_Warning_: Deposits _must_ be signed with `GENESIS_FORK_VERSION`, calling `compute_domain` without a second argument defaults to the correct version).
- Let `deposit_data_root` be `hash_tree_root(deposit_data)`. - Let `deposit_data_root` be `hash_tree_root(deposit_data)`.
- 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], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei. - 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], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei.
@ -129,13 +132,13 @@ Deposits cannot be processed into the beacon chain until the Eth1 block in which
### Validator index ### Validator index
Once a validator has been processed and added to the beacon state's `validators`, the validator's `validator_index` is defined by the index into the registry at which the [`ValidatorRecord`](../core/0_beacon-chain.md#validator) contains the `pubkey` specified in the validator's deposit. A validator's `validator_index` is guaranteed to not change from the time of initial deposit until the validator exits and fully withdraws. This `validator_index` is used throughout the specification to dictate validator roles and responsibilities at any point and should be stored locally. Once a validator has been processed and added to the beacon state's `validators`, the validator's `validator_index` is defined by the index into the registry at which the [`ValidatorRecord`](./beacon-chain.md#validator) contains the `pubkey` specified in the validator's deposit. A validator's `validator_index` is guaranteed to not change from the time of initial deposit until the validator exits and fully withdraws. This `validator_index` is used throughout the specification to dictate validator roles and responsibilities at any point and should be stored locally.
### Activation ### Activation
In normal operation, the validator is quickly activated, at which point the validator is added to the shuffling and begins validation after an additional `MAX_SEED_LOOKAHEAD` epochs (25.6 minutes). In normal operation, the validator is quickly activated, at which point the validator is added to the shuffling and begins validation after an additional `MAX_SEED_LOOKAHEAD` epochs (25.6 minutes).
The function [`is_active_validator`](../core/0_beacon-chain.md#is_active_validator) can be used to check if a validator is active during a given epoch. Usage is as follows: The function [`is_active_validator`](./beacon-chain.md#is_active_validator) can be used to check if a validator is active during a given epoch. Usage is as follows:
```python ```python
def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool:
@ -206,7 +209,7 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo
### Block proposal ### Block proposal
A validator is expected to propose a [`SignedBeaconBlock`](../core/0_beacon-chain.md#signedbeaconblock) at the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](../core/0_beacon-chain.md#beacon-chain-state-transition-function). A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function).
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks). There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks).
@ -234,58 +237,90 @@ Set `block.body.randao_reveal = epoch_signature` where `epoch_signature` is obta
```python ```python
def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot)) domain = get_domain(state, DOMAIN_RANDAO, compute_epoch_at_slot(block.slot))
return bls_sign(privkey, hash_tree_root(compute_epoch_at_slot(block.slot)), domain) signing_root = compute_signing_root(compute_epoch_at_slot(block.slot), domain)
return bls.Sign(privkey, signing_root)
``` ```
##### Eth1 Data ##### Eth1 Data
The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data. This recent data contains an Eth1 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth1 block. If over half of the block proposers in the current Eth1 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`. The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data. This recent data contains an Eth1 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth1 block. If over half of the block proposers in the current Eth1 voting period vote for the same `eth1_data` then `state.eth1_data` updates immediately allowing new deposits to be processed. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
Let `get_eth1_data(distance: uint64) -> Eth1Data` be the (subjective) function that returns the Eth1 data at distance `distance` relative to the Eth1 head at the start of the current Eth1 voting period. Let `previous_eth1_distance` be the distance relative to the Eth1 block corresponding to `eth1_data.block_hash` found in the state at the _start_ of the current Eth1 voting period. Note that `eth1_data` can be updated in the middle of a voting period and thus the starting `eth1_data.block_hash` must be stored separately. ###### `Eth1Block`
An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where: Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` field available.
```python ```python
def get_eth1_vote(state: BeaconState, previous_eth1_distance: uint64) -> Eth1Data: class Eth1Block(Container):
new_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, 2 * ETH1_FOLLOW_DISTANCE)] timestamp: uint64
all_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, previous_eth1_distance)] # All other eth1 block fields
```
period_tail = state.slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_squareroot(SLOTS_PER_ETH1_VOTING_PERIOD) ###### `get_eth1_data`
if period_tail:
votes_to_consider = all_eth1_data
else:
votes_to_consider = new_eth1_data
Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block.
An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state)` where:
```python
def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64:
return state.genesis_time + slot * SECONDS_PER_SLOT
```
```python
def voting_period_start_time(state: BeaconState) -> uint64:
eth1_voting_period_start_slot = Slot(state.slot - state.slot % SLOTS_PER_ETH1_VOTING_PERIOD)
return compute_time_at_slot(state, eth1_voting_period_start_slot)
```
```python
def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool:
return (
block.timestamp <= period_start - SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE
and block.timestamp >= period_start - SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE * 2
)
```
```python
def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data:
period_start = voting_period_start_time(state)
# `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height
votes_to_consider = [get_eth1_data(block) for block in eth1_chain if
is_candidate_block(block, period_start)]
# Valid votes already cast during this period
valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider]
# Default vote on latest eth1 block data in the period range unless eth1 chain is not live
default_vote = votes_to_consider[-1] if any(votes_to_consider) else state.eth1_data
return max( return max(
valid_votes, valid_votes,
key=lambda v: (valid_votes.count(v), -all_eth1_data.index(v)), # Tiebreak by smallest distance key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance
default=get_eth1_data(ETH1_FOLLOW_DISTANCE), default=default_vote
) )
``` ```
##### Proposer slashings ##### Proposer slashings
Up to `MAX_PROPOSER_SLASHINGS`, [`ProposerSlashing`](../core/0_beacon-chain.md#proposerslashing) objects can be included in the `block`. The proposer slashings must satisfy the verification conditions found in [proposer slashings processing](../core/0_beacon-chain.md#proposer-slashings). The validator receives a small "whistleblower" reward for each proposer slashing found and included. Up to `MAX_PROPOSER_SLASHINGS`, [`ProposerSlashing`](./beacon-chain.md#proposerslashing) objects can be included in the `block`. The proposer slashings must satisfy the verification conditions found in [proposer slashings processing](./beacon-chain.md#proposer-slashings). The validator receives a small "whistleblower" reward for each proposer slashing found and included.
##### Attester slashings ##### Attester slashings
Up to `MAX_ATTESTER_SLASHINGS`, [`AttesterSlashing`](../core/0_beacon-chain.md#attesterslashing) objects can be included in the `block`. The attester slashings must satisfy the verification conditions found in [attester slashings processing](../core/0_beacon-chain.md#attester-slashings). The validator receives a small "whistleblower" reward for each attester slashing found and included. Up to `MAX_ATTESTER_SLASHINGS`, [`AttesterSlashing`](./beacon-chain.md#attesterslashing) objects can be included in the `block`. The attester slashings must satisfy the verification conditions found in [attester slashings processing](./beacon-chain.md#attester-slashings). The validator receives a small "whistleblower" reward for each attester slashing found and included.
##### Attestations ##### Attestations
Up to `MAX_ATTESTATIONS`, aggregate attestations can be included in the `block`. The attestations added must satisfy the verification conditions found in [attestation processing](../core/0_beacon-chain.md#attestations). To maximize profit, the validator should attempt to gather aggregate attestations that include singular attestations from the largest number of validators whose signatures from the same epoch have not previously been added on chain. Up to `MAX_ATTESTATIONS`, aggregate attestations can be included in the `block`. The attestations added must satisfy the verification conditions found in [attestation processing](./beacon-chain.md#attestations). To maximize profit, the validator should attempt to gather aggregate attestations that include singular attestations from the largest number of validators whose signatures from the same epoch have not previously been added on chain.
##### Deposits ##### Deposits
If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. `state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. These [`deposits`](../core/0_beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth1 deposit contract](../core/0_deposit-contract.md) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](../core/0_beacon-chain.md#deposits). If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. `state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. These [`deposits`](./beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [Eth1 deposit contract](./deposit-contract.md) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](./beacon-chain.md#deposits).
The `proof` for each deposit must be constructed against the deposit root contained in `state.eth1_data` rather than the deposit root at the time the deposit was initially logged from the 1.0 chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation. The `proof` for each deposit must be constructed against the deposit root contained in `state.eth1_data` rather than the deposit root at the time the deposit was initially logged from the 1.0 chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation.
##### Voluntary exits ##### Voluntary exits
Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](../core/0_beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](../core/0_beacon-chain.md#voluntary-exits). Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](./beacon-chain.md#voluntary-exits).
#### Packaging into a `SignedBeaconBlock` #### Packaging into a `SignedBeaconBlock`
@ -311,20 +346,21 @@ def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root:
```python ```python
def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey: int) -> BLSSignature: def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(header.slot)) domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(header.slot))
return bls_sign(privkey, hash_tree_root(header), domain) signing_root = compute_signing_root(header, domain)
return bls.Sign(privkey, signing_root)
``` ```
### Attesting ### Attesting
A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`. A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`.
A validator should create and broadcast the `attestation` to the associated attestation subnet one-third of the way through the `slot` during which the validator is assigned―that is, `SECONDS_PER_SLOT / 3` seconds after the start of `slot`. A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid block from the expected block proposer for the assigned `slot` or (b) one-third of the `slot` hash transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_.
*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded fork, and should be made. *Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded fork, and should be made.
#### Attestation data #### Attestation data
First, the validator should construct `attestation_data`, an [`AttestationData`](../core/0_beacon-chain.md#attestationdata) object based upon the state at the assigned slot. First, the validator should construct `attestation_data`, an [`AttestationData`](./beacon-chain.md#attestationdata) object based upon the state at the assigned slot.
- Let `head_block` be the result of running the fork choice during the assigned slot. - Let `head_block` be the result of running the fork choice during the assigned slot.
- Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. - Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`.
@ -350,7 +386,7 @@ Set `attestation_data.beacon_block_root = hash_tree_root(head_block)`.
#### Construct attestation #### Construct attestation
Next, the validator creates `attestation`, an [`Attestation`](../core/0_beacon-chain.md#attestation) object. Next, the validator creates `attestation`, an [`Attestation`](./beacon-chain.md#attestation) object.
##### Data ##### Data
@ -369,7 +405,8 @@ Set `attestation.signature = signed_attestation_data` where `signed_attestation_
```python ```python
def get_signed_attestation_data(state: BeaconState, attestation: IndexedAttestation, privkey: int) -> BLSSignature: def get_signed_attestation_data(state: BeaconState, attestation: IndexedAttestation, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch)
return bls_sign(privkey, hash_tree_root(attestation.data), domain) signing_root = compute_signing_root(attestation.data, domain)
return bls.Sign(privkey, signing_root)
``` ```
#### Broadcast attestation #### Broadcast attestation
@ -387,7 +424,8 @@ A validator is selected to aggregate based upon the return value of `is_aggregat
```python ```python
def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, compute_epoch_at_slot(slot)) domain = get_domain(state, DOMAIN_BEACON_ATTESTER, compute_epoch_at_slot(slot))
return bls_sign(privkey, hash_tree_root(slot), domain) signing_root = compute_signing_root(slot, domain)
return bls.Sign(privkey, signing_root)
``` ```
```python ```python
@ -418,7 +456,7 @@ Set `aggregate_attestation.signature = aggregate_signature` where `aggregate_sig
```python ```python
def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature:
signatures = [attestation.signature for attestation in attestations] signatures = [attestation.signature for attestation in attestations]
return bls_aggregate_signatures(signatures) return bls.Aggregate(signatures)
``` ```
#### Broadcast aggregate #### Broadcast aggregate
@ -457,7 +495,7 @@ Because Phase 0 does not have shards and thus does not have Shard Committees, th
### Proposer slashing ### Proposer slashing
To avoid "proposer slashings", a validator must not sign two conflicting [`BeaconBlock`](../core/0_beacon-chain.md#beaconblock) where conflicting is defined as two distinct blocks within the same epoch. To avoid "proposer slashings", a validator must not sign two conflicting [`BeaconBlock`](./beacon-chain.md#beaconblock) where conflicting is defined as two distinct blocks within the same epoch.
*In Phase 0, as long as the validator does not sign two different beacon blocks for the same epoch, the validator is safe against proposer slashings.* *In Phase 0, as long as the validator does not sign two different beacon blocks for the same epoch, the validator is safe against proposer slashings.*
@ -470,7 +508,7 @@ If the software crashes at some point within this routine, then when the validat
### Attester slashing ### Attester slashing
To avoid "attester slashings", a validator must not sign two conflicting [`AttestationData`](../core/0_beacon-chain.md#attestationdata) objects, i.e. two attestations that satisfy [`is_slashable_attestation_data`](../core/0_beacon-chain.md#is_slashable_attestation_data). To avoid "attester slashings", a validator must not sign two conflicting [`AttestationData`](./beacon-chain.md#attestationdata) objects, i.e. two attestations that satisfy [`is_slashable_attestation_data`](./beacon-chain.md#is_slashable_attestation_data).
Specifically, when signing an `Attestation`, a validator should perform the following steps in the following order: Specifically, when signing an `Attestation`, a validator should perform the following steps in the following order:

View File

@ -59,7 +59,7 @@
## Introduction ## Introduction
This document details the beacon chain additions and changes in Phase 1 of Ethereum 2.0 to support the shard data custody game, building upon the [Phase 0](0_beacon-chain.md) specification. This document details the beacon chain additions and changes in Phase 1 of Ethereum 2.0 to support the shard data custody game, building upon the [Phase 0](../phase0/beacon-chain.md) specification.
## Terminology ## Terminology
@ -131,7 +131,7 @@ The following types are defined, mapping into `DomainType` (little endian):
| Name | Value | | Name | Value |
| - | - | | - | - |
| `DOMAIN_CUSTODY_BIT_CHALLENGE` | `6` | | `DOMAIN_CUSTODY_BIT_CHALLENGE` | `DomainType('0x06000000')` |
### TODO PLACEHOLDER ### TODO PLACEHOLDER
@ -197,7 +197,7 @@ class CustodyBitChallengeRecord(Container):
class CustodyResponse(Container): class CustodyResponse(Container):
challenge_index: uint64 challenge_index: uint64
chunk_index: uint64 chunk_index: uint64
chunk: BytesN[BYTES_PER_CUSTODY_CHUNK] chunk: ByteVector[BYTES_PER_CUSTODY_CHUNK]
data_branch: List[Bytes32, CUSTODY_DATA_DEPTH] data_branch: List[Bytes32, CUSTODY_DATA_DEPTH]
chunk_bits_branch: List[Bytes32, CUSTODY_CHUNK_BIT_DEPTH] chunk_bits_branch: List[Bytes32, CUSTODY_CHUNK_BIT_DEPTH]
chunk_bits_leaf: Bitvector[256] chunk_bits_leaf: Bitvector[256]
@ -353,7 +353,7 @@ def custody_subchunkify(bytez: bytes) -> Sequence[bytes]:
```python ```python
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool: def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
full_G2_element = bls_signature_to_G2(key) full_G2_element = bls.signature_to_G2(key)
s = full_G2_element[0].coeffs s = full_G2_element[0].coeffs
bits = [legendre_bit((i + 1) * s[i % 2] + int.from_bytes(subchunk, "little"), BLS12_381_Q) bits = [legendre_bit((i + 1) * s[i % 2] + int.from_bytes(subchunk, "little"), BLS12_381_Q)
for i, subchunk in enumerate(custody_subchunkify(chunk))] for i, subchunk in enumerate(custody_subchunkify(chunk))]
@ -429,16 +429,9 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
assert is_slashable_validator(revealer, get_current_epoch(state)) assert is_slashable_validator(revealer, get_current_epoch(state))
# Verify signature # Verify signature
assert bls_verify( domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
pubkey=revealer.pubkey, signing_root = compute_signing_root(epoch_to_sign, domain)
message_hash=hash_tree_root(epoch_to_sign), assert bls.Verify(revealer.pubkey, signing_root, reveal.reveal)
signature=reveal.reveal,
domain=get_domain(
state=state,
domain_type=DOMAIN_RANDAO,
message_epoch=epoch_to_sign,
),
)
# Decrement max reveal lateness if response is timely # Decrement max reveal lateness if response is timely
if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state): if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state):
@ -487,21 +480,10 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived
# Verify signature correctness # Verify signature correctness
masker = state.validators[reveal.masker_index] masker = state.validators[reveal.masker_index]
pubkeys = [revealed_validator.pubkey, masker.pubkey] pubkeys = [revealed_validator.pubkey, masker.pubkey]
message_hashes = [
hash_tree_root(reveal.epoch),
reveal.mask,
]
assert bls_verify_multiple( domain = get_domain(state, DOMAIN_RANDAO, reveal.epoch)
pubkeys=pubkeys, signing_roots = [compute_signing_root(root, domain) for root in [hash_tree_root(reveal.epoch), reveal.mask]]
message_hashes=message_hashes, assert bls.AggregateVerify(zip(pubkeys, signing_roots), reveal.reveal)
signature=reveal.reveal,
domain=get_domain(
state=state,
domain_type=DOMAIN_RANDAO,
message_epoch=reveal.epoch,
),
)
if reveal.epoch >= get_current_epoch(state) + CUSTODY_PERIOD_TO_RANDAO_PADDING: if reveal.epoch >= get_current_epoch(state) + CUSTODY_PERIOD_TO_RANDAO_PADDING:
# Full slashing when the secret was revealed so early it may be a valid custody # Full slashing when the secret was revealed so early it may be a valid custody
@ -598,7 +580,7 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) ->
challenger = state.validators[challenge.challenger_index] challenger = state.validators[challenge.challenger_index]
domain = get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state)) domain = get_domain(state, DOMAIN_CUSTODY_BIT_CHALLENGE, get_current_epoch(state))
# TODO incorrect hash-tree-root, but this changes with phase 1 PR #1483 # TODO incorrect hash-tree-root, but this changes with phase 1 PR #1483
assert bls_verify(challenger.pubkey, hash_tree_root(challenge), challenge.signature, domain) assert bls.Verify(challenger.pubkey, compute_signing_root(challenge, domain), challenge.signature)
# Verify challenger is slashable # Verify challenger is slashable
assert is_slashable_validator(challenger, get_current_epoch(state)) assert is_slashable_validator(challenger, get_current_epoch(state))
# Verify attestation # Verify attestation
@ -622,7 +604,7 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) ->
challenge.responder_index, challenge.responder_index,
) )
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign) domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
assert bls_verify(responder.pubkey, hash_tree_root(epoch_to_sign), challenge.responder_key, domain) assert bls.Verify(responder.pubkey, compute_signing_root(epoch_to_sign, domain), challenge.responder_key)
# Verify the chunk count # Verify the chunk count
chunk_count = get_custody_chunk_count(attestation.data.crosslink) chunk_count = get_custody_chunk_count(attestation.data.crosslink)
assert chunk_count == len(challenge.chunk_bits) assert chunk_count == len(challenge.chunk_bits)

View File

@ -135,9 +135,10 @@ def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None:
assert 3 * sum(filter(lambda i: update.aggregation_bits[i], balances)) > 2 * sum(balances) assert 3 * sum(filter(lambda i: update.aggregation_bits[i], balances)) > 2 * sum(balances)
# Verify shard attestations # Verify shard attestations
pubkey = bls_aggregate_pubkeys(filter(lambda i: update.aggregation_bits[i], pubkeys)) pubkeys = filter(lambda i: update.aggregation_bits[i], pubkeys)
domain = compute_domain(DOMAIN_SHARD_ATTESTER, update.fork_version) domain = compute_domain(DOMAIN_SHARD_ATTESTER, update.fork_version)
assert bls_verify(pubkey, update.shard_block_root, update.signature, domain) signing_root = compute_signing_root(update.shard_block_root, domain)
assert bls.FastAggregateVerify(pubkeys, signing_root, update.signature)
# Update period committees if entering a new period # Update period committees if entering a new period
if next_period == current_period + 1: if next_period == current_period + 1:

View File

@ -101,8 +101,8 @@ This document describes the shard transition function (data layer only) and the
| Name | Value | | Name | Value |
| - | - | | - | - |
| `DOMAIN_SHARD_PROPOSER` | `128` | | `DOMAIN_SHARD_PROPOSER` | `DomainType('0x80000000')` |
| `DOMAIN_SHARD_ATTESTER` | `129` | | `DOMAIN_SHARD_ATTESTER` | `DomainType('0x81000000')` |
## Containers ## Containers
@ -386,7 +386,7 @@ def process_shard_block_header(beacon_state: BeaconState, shard_state: ShardStat
assert not proposer.slashed assert not proposer.slashed
# Verify proposer signature # Verify proposer signature
domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot)) domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot))
assert bls_verify(proposer.pubkey, hash_tree_root(block), block.signature, domain) assert bls.Verify(proposer.pubkey, compute_signing_root(block, domain), block.signature)
``` ```
#### Attestations #### Attestations
@ -406,8 +406,9 @@ def process_shard_attestations(beacon_state: BeaconState, shard_state: ShardStat
assert block.aggregation_bits[i] == 0b0 assert block.aggregation_bits[i] == 0b0
# Verify attester aggregate signature # Verify attester aggregate signature
domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot)) domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot))
message = hash_tree_root(ShardAttestationData(slot=shard_state.slot, parent_root=block.parent_root)) shard_attestation_data = ShardAttestationData(slot=shard_state.slot, parent_root=block.parent_root)
assert bls_verify(bls_aggregate_pubkeys(pubkeys), message, block.attestations, domain) signing_root = compute_signing_root(shard_attestation_data, domain)
assert bls.FastAggregateVerify(pubkeys, signing_root, block.attestations)
# Proposer micro-reward # Proposer micro-reward
proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot)
reward = attestation_count * get_base_reward(beacon_state, proposer_index) // PROPOSER_REWARD_QUOTIENT reward = attestation_count * get_base_reward(beacon_state, proposer_index) // PROPOSER_REWARD_QUOTIENT

View File

@ -164,11 +164,11 @@ def get_generalized_index(typ: SSZType, path: Sequence[Union[int, SSZVariableNam
assert not issubclass(typ, BasicValue) # If we descend to a basic type, the path cannot continue further assert not issubclass(typ, BasicValue) # If we descend to a basic type, the path cannot continue further
if p == '__len__': if p == '__len__':
typ = uint64 typ = uint64
assert issubclass(typ, (List, Bytes)) assert issubclass(typ, (List, ByteList))
root = GeneralizedIndex(root * 2 + 1) root = GeneralizedIndex(root * 2 + 1)
else: else:
pos, _, _ = get_item_position(typ, p) pos, _, _ = get_item_position(typ, p)
base_index = (GeneralizedIndex(2) if issubclass(typ, (List, Bytes)) else GeneralizedIndex(1)) base_index = (GeneralizedIndex(2) if issubclass(typ, (List, ByteList)) else GeneralizedIndex(1))
root = GeneralizedIndex(root * base_index * get_next_power_of_two(chunk_count(typ)) + pos) root = GeneralizedIndex(root * base_index * get_next_power_of_two(chunk_count(typ)) + pos)
typ = get_elem_type(typ, p) typ = get_elem_type(typ, p)
return root return root

View File

@ -239,7 +239,7 @@ We now define Merkleization `hash_tree_root(value)` of an object `value` recursi
Let `A` be an object derived from another object `B` by replacing some of the (possibly nested) values of `B` by their `hash_tree_root`. We say `A` is a "summary" of `B`, and that `B` is an "expansion" of `A`. Notice `hash_tree_root(A) == hash_tree_root(B)`. Let `A` be an object derived from another object `B` by replacing some of the (possibly nested) values of `B` by their `hash_tree_root`. We say `A` is a "summary" of `B`, and that `B` is an "expansion" of `A`. Notice `hash_tree_root(A) == hash_tree_root(B)`.
We similarly define "summary types" and "expansion types". For example, [`BeaconBlock`](./core/0_beacon-chain.md#beaconblock) is an expansion type of [`BeaconBlockHeader`](./core/0_beacon-chain.md#beaconblockheader). Notice that objects expand to at most one object of a given expansion type. For example, `BeaconBlockHeader` objects uniquely expand to `BeaconBlock` objects. We similarly define "summary types" and "expansion types". For example, [`BeaconBlock`](../specs/phase0/beacon-chain.md#beaconblock) is an expansion type of [`BeaconBlockHeader`](../specs/phase0/beacon-chain.md#beaconblockheader). Notice that objects expand to at most one object of a given expansion type. For example, `BeaconBlockHeader` objects uniquely expand to `BeaconBlock` objects.
## Implementations ## Implementations

View File

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

View File

@ -1,3 +0,0 @@
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -1,3 +0,0 @@
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

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

View File

@ -1,3 +0,0 @@
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

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

View File

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

View File

@ -1,3 +0,0 @@
../../test_libs/gen_helpers
../../test_libs/config_helpers
../../test_libs/pyspec

View File

@ -1,17 +0,0 @@
from eth2spec.utils.bls import bls_sign
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
def sign_voluntary_exit(spec, state, voluntary_exit, privkey):
return spec.SignedVoluntaryExit(
message=voluntary_exit,
signature=bls_sign(
message_hash=hash_tree_root(voluntary_exit),
privkey=privkey,
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_VOLUNTARY_EXIT,
message_epoch=voluntary_exit.epoch,
)
)
)

View File

@ -1,55 +0,0 @@
from py_ecc import bls
# Flag to make BLS active or not. Used for testing, do not ignore BLS in production unless you know what you are doing.
bls_active = True
STUB_SIGNATURE = b'\x11' * 96
STUB_PUBKEY = b'\x22' * 48
STUB_COORDINATES = bls.api.signature_to_G2(bls.sign(b"", 0, b"\0" * 8))
def only_with_bls(alt_return=None):
"""
Decorator factory to make a function only run when BLS is active. Otherwise return the default.
"""
def runner(fn):
def entry(*args, **kw):
if bls_active:
return fn(*args, **kw)
else:
return alt_return
return entry
return runner
@only_with_bls(alt_return=True)
def bls_verify(pubkey, message_hash, signature, domain):
return bls.verify(message_hash=message_hash, pubkey=pubkey,
signature=signature, domain=domain)
@only_with_bls(alt_return=True)
def bls_verify_multiple(pubkeys, message_hashes, signature, domain):
return bls.verify_multiple(pubkeys=pubkeys, message_hashes=message_hashes,
signature=signature, domain=domain)
@only_with_bls(alt_return=STUB_PUBKEY)
def bls_aggregate_pubkeys(pubkeys):
return bls.aggregate_pubkeys(pubkeys)
@only_with_bls(alt_return=STUB_SIGNATURE)
def bls_aggregate_signatures(signatures):
return bls.aggregate_signatures(signatures)
@only_with_bls(alt_return=STUB_SIGNATURE)
def bls_sign(message_hash, privkey, domain):
return bls.sign(message_hash=message_hash, privkey=privkey,
domain=domain)
@only_with_bls(alt_return=STUB_COORDINATES)
def bls_signature_to_G2(signature):
return bls.api.signature_to_G2(signature)

View File

@ -1,7 +1,7 @@
# Eth2 config helpers # Eth2 config helpers
`preset_loader`: A util to load constants-presets with. `preset_loader`: A util to load config-presets with.
See [Constants-presets documentation](../../configs/constants_presets/README.md). See [Configs documentation](../../../configs/README.md).
Usage: Usage:

View File

@ -4,7 +4,7 @@
A util to quickly write new test suite generators with. A util to quickly write new test suite generators with.
See [Generators documentation](../../test_generators/README.md) for integration details. See [Generators documentation](../../generators/README.md) for integration details.
Options: Options:

View File

@ -63,4 +63,4 @@ The pyspec is not a replacement.
## License ## License
Same as the spec itself; see [LICENSE](../../LICENSE) file in the specs repository root. Same as the spec itself; see [LICENSE](../../../LICENSE) file in the specs repository root.

View File

@ -1,8 +1,8 @@
from typing import Any from typing import Any
from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
SSZType, SSZValue, uint, Container, Bytes, List, boolean, SSZType, SSZValue, uint, Container, ByteList, List, boolean,
Vector, BytesN Vector, ByteVector
) )
@ -11,7 +11,7 @@ def decode(data: Any, typ: SSZType) -> SSZValue:
return typ(data) return typ(data)
elif issubclass(typ, (List, Vector)): elif issubclass(typ, (List, Vector)):
return typ(decode(element, typ.elem_type) for element in data) return typ(decode(element, typ.elem_type) for element in data)
elif issubclass(typ, (Bytes, BytesN)): elif issubclass(typ, (ByteList, ByteVector)):
return typ(bytes.fromhex(data[2:])) return typ(bytes.fromhex(data[2:]))
elif issubclass(typ, Container): elif issubclass(typ, Container):
temp = {} temp = {}

View File

@ -17,7 +17,7 @@ def encode(value, include_hash_tree_roots=False):
return '0x' + serialize(value).hex() return '0x' + serialize(value).hex()
elif isinstance(value, list): # normal python lists, ssz-List, Vector elif isinstance(value, list): # normal python lists, ssz-List, Vector
return [encode(element, include_hash_tree_roots) for element in value] return [encode(element, include_hash_tree_roots) for element in value]
elif isinstance(value, bytes): # both bytes and BytesN elif isinstance(value, bytes): # both bytes and ByteVector
return '0x' + value.hex() return '0x' + value.hex()
elif isinstance(value, Container): elif isinstance(value, Container):
ret = {} ret = {}

View File

@ -2,8 +2,8 @@ from random import Random
from enum import Enum from enum import Enum
from eth2spec.utils.ssz.ssz_typing import ( from eth2spec.utils.ssz.ssz_typing import (
SSZType, SSZValue, BasicValue, BasicType, uint, Container, Bytes, List, boolean, SSZType, SSZValue, BasicValue, BasicType, uint, Container, ByteList, List, boolean,
Vector, BytesN, Bitlist, Bitvector Vector, ByteVector, Bitlist, Bitvector
) )
# in bytes # in bytes
@ -51,8 +51,8 @@ def get_random_ssz_object(rng: Random,
""" """
if chaos: if chaos:
mode = rng.choice(list(RandomizationMode)) mode = rng.choice(list(RandomizationMode))
if issubclass(typ, Bytes): if issubclass(typ, ByteList):
# Bytes array # ByteList array
if mode == RandomizationMode.mode_nil_count: if mode == RandomizationMode.mode_nil_count:
return typ(b'') return typ(b'')
elif mode == RandomizationMode.mode_max_count: elif mode == RandomizationMode.mode_max_count:
@ -65,7 +65,7 @@ def get_random_ssz_object(rng: Random,
return typ(b'\xff' * min(1, typ.length)) return typ(b'\xff' * min(1, typ.length))
else: else:
return typ(get_random_bytes_list(rng, rng.randint(0, min(max_bytes_length, typ.length)))) return typ(get_random_bytes_list(rng, rng.randint(0, min(max_bytes_length, typ.length))))
elif issubclass(typ, BytesN): elif issubclass(typ, ByteVector):
# Sanity, don't generate absurdly big random values # Sanity, don't generate absurdly big random values
# If a client is aiming to performance-test, they should create a benchmark suite. # If a client is aiming to performance-test, they should create a benchmark suite.
assert typ.length <= max_bytes_length assert typ.length <= max_bytes_length

View File

@ -11,9 +11,9 @@ def translate_typ(typ) -> ssz.BaseSedes:
if issubclass(typ, spec_ssz.Container): if issubclass(typ, spec_ssz.Container):
return ssz.Container( return ssz.Container(
[translate_typ(field_typ) for field_name, field_typ in typ.get_fields().items()]) [translate_typ(field_typ) for field_name, field_typ in typ.get_fields().items()])
elif issubclass(typ, spec_ssz.BytesN): elif issubclass(typ, spec_ssz.ByteVector):
return ssz.ByteVector(typ.length) return ssz.ByteVector(typ.length)
elif issubclass(typ, spec_ssz.Bytes): elif issubclass(typ, spec_ssz.ByteList):
return ssz.ByteList() return ssz.ByteList()
elif issubclass(typ, spec_ssz.Vector): elif issubclass(typ, spec_ssz.Vector):
return ssz.Vector(translate_typ(typ.elem_type), typ.length) return ssz.Vector(translate_typ(typ.elem_type), typ.length)
@ -76,9 +76,9 @@ def translate_value(value, typ):
return typ(value) return typ(value)
elif issubclass(typ, spec_ssz.Bitvector): elif issubclass(typ, spec_ssz.Bitvector):
return typ(value) return typ(value)
elif issubclass(typ, spec_ssz.BytesN): elif issubclass(typ, spec_ssz.ByteVector):
return typ(value) return typ(value)
elif issubclass(typ, spec_ssz.Bytes): elif issubclass(typ, spec_ssz.ByteList):
return value return value
if issubclass(typ, spec_ssz.Container): if issubclass(typ, spec_ssz.Container):
return typ(**{f_name: translate_value(f_val, f_typ) for (f_val, (f_name, f_typ)) return typ(**{f_name: translate_value(f_val, f_typ) for (f_val, (f_name, f_typ))

View File

@ -34,6 +34,6 @@ def pytest_addoption(parser):
def config(request): def config(request):
config_name = request.config.getoption("--config") config_name = request.config.getoption("--config")
from preset_loader import loader from preset_loader import loader
presets = loader.load_presets('../../configs/', config_name) presets = loader.load_presets('../../../configs/', config_name)
spec_phase0.apply_constants_preset(presets) spec_phase0.apply_constants_preset(presets)
spec_phase1.apply_constants_preset(presets) spec_phase1.apply_constants_preset(presets)

View File

@ -20,7 +20,7 @@ def test_initialize_beacon_state_from_eth1(spec):
# initialize beacon_state # initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.SECONDS_PER_DAY + 2 * spec.SECONDS_PER_DAY assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY
assert len(state.validators) == deposit_count assert len(state.validators) == deposit_count
assert state.eth1_data.deposit_root == deposit_root assert state.eth1_data.deposit_root == deposit_root
assert state.eth1_data.deposit_count == deposit_count assert state.eth1_data.deposit_count == deposit_count
@ -55,7 +55,7 @@ def test_initialize_beacon_state_some_small_balances(spec):
# initialize beacon_state # initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.SECONDS_PER_DAY + 2 * spec.SECONDS_PER_DAY assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY
assert len(state.validators) == small_deposit_count assert len(state.validators) == small_deposit_count
assert state.eth1_data.deposit_root == deposit_root assert state.eth1_data.deposit_root == deposit_root
assert state.eth1_data.deposit_count == len(deposits) assert state.eth1_data.deposit_count == len(deposits)

View File

@ -3,7 +3,7 @@ from typing import List
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \ from eth2spec.test.helpers.block import build_empty_block_for_next_slot, transition_unsigned_block, \
build_empty_block build_empty_block
from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_typing import Bitlist from eth2spec.utils.ssz.ssz_typing import Bitlist
@ -77,8 +77,7 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List
privkey privkey
) )
) )
return bls.Aggregate(signatures)
return bls_aggregate_signatures(signatures)
def sign_indexed_attestation(spec, state, indexed_attestation): def sign_indexed_attestation(spec, state, indexed_attestation):
@ -97,15 +96,9 @@ def sign_attestation(spec, state, attestation):
def get_attestation_signature(spec, state, attestation_data, privkey): def get_attestation_signature(spec, state, attestation_data, privkey):
return bls_sign( domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
message_hash=attestation_data.hash_tree_root(), signing_root = spec.compute_signing_root(attestation_data, domain)
privkey=privkey, return bls.Sign(privkey, signing_root)
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_BEACON_ATTESTER,
message_epoch=attestation_data.target.epoch,
)
)
def fill_aggregate_attestation(spec, state, attestation, signed=False): def fill_aggregate_attestation(spec, state, attestation, signed=False):

View File

@ -1,7 +1,8 @@
from copy import deepcopy from copy import deepcopy
from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import bls_sign, only_with_bls from eth2spec.utils import bls
from eth2spec.utils.bls import only_with_bls
from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_impl import hash_tree_root
@ -28,15 +29,9 @@ def apply_randao_reveal(spec, state, block, proposer_index=None):
proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index) proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index)
privkey = privkeys[proposer_index] privkey = privkeys[proposer_index]
block.body.randao_reveal = bls_sign( domain = spec.get_domain(state, spec.DOMAIN_RANDAO, spec.compute_epoch_at_slot(block.slot))
privkey=privkey, signing_root = spec.compute_signing_root(spec.compute_epoch_at_slot(block.slot), domain)
message_hash=hash_tree_root(spec.compute_epoch_at_slot(block.slot)), block.body.randao_reveal = bls.Sign(privkey, signing_root)
domain=spec.get_domain(
state,
message_epoch=spec.compute_epoch_at_slot(block.slot),
domain_type=spec.DOMAIN_RANDAO,
)
)
# Fully ignore the function if BLS is off, beacon-proposer index calculation is slow. # Fully ignore the function if BLS is off, beacon-proposer index calculation is slow.
@ -46,14 +41,10 @@ def apply_sig(spec, state, signed_block, proposer_index=None):
proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index) proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index)
privkey = privkeys[proposer_index] privkey = privkeys[proposer_index]
domain = spec.get_domain(state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot))
signing_root = spec.compute_signing_root(block, domain)
signed_block.signature = bls_sign( signed_block.signature = bls.Sign(privkey, signing_root)
message_hash=hash_tree_root(block),
privkey=privkey,
domain=spec.get_domain(
state,
spec.DOMAIN_BEACON_PROPOSER,
spec.compute_epoch_at_slot(block.slot)))
def sign_block(spec, state, block, proposer_index=None): def sign_block(spec, state, block, proposer_index=None):

View File

@ -1,5 +1,4 @@
from eth2spec.utils.bls import bls_sign from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
def sign_block_header(spec, state, header, privkey): def sign_block_header(spec, state, header, privkey):
@ -7,8 +6,6 @@ def sign_block_header(spec, state, header, privkey):
state=state, state=state,
domain_type=spec.DOMAIN_BEACON_PROPOSER, domain_type=spec.DOMAIN_BEACON_PROPOSER,
) )
return spec.SignedBeaconBlockHeader(message=header, signature=bls_sign( signing_root = spec.compute_signing_root(header, domain)
message_hash=hash_tree_root(header), signature = bls.Sign(privkey, signing_root)
privkey=privkey, return spec.SignedBeaconBlockHeader(message=header, signature=signature)
domain=domain,
))

View File

@ -1,7 +1,7 @@
from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures from eth2spec.utils import bls
from eth2spec.utils.hash_function import hash from eth2spec.utils.hash_function import hash
from eth2spec.utils.ssz.ssz_typing import Bitlist, BytesN, Bitvector from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, Bitvector
from eth2spec.utils.ssz.ssz_impl import chunkify, pack, hash_tree_root from eth2spec.utils.ssz.ssz_impl import chunkify, pack, hash_tree_root
from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
@ -17,28 +17,15 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
epoch = current_epoch + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING epoch = current_epoch + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING
# Generate the secret that is being revealed # Generate the secret that is being revealed
reveal = bls_sign( domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch)
message_hash=hash_tree_root(spec.Epoch(epoch)), signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain)
privkey=privkeys[revealed_index], reveal = bls.Sign(privkeys[revealed_index], signing_root)
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_RANDAO,
message_epoch=epoch,
),
)
# Generate the mask (any random 32 bytes that don't reveal the masker's secret will do) # Generate the mask (any random 32 bytes that don't reveal the masker's secret will do)
mask = hash(reveal) mask = hash(reveal)
# Generate masker's signature on the mask # Generate masker's signature on the mask
masker_signature = bls_sign( signing_root = spec.compute_signing_root(mask, domain)
message_hash=mask, masker_signature = bls.Sign(privkeys[masker_index], signing_root)
privkey=privkeys[masker_index], masked_reveal = bls.Aggregate([reveal, masker_signature])
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_RANDAO,
message_epoch=epoch,
),
)
masked_reveal = bls_aggregate_signatures([reveal, masker_signature])
return spec.EarlyDerivedSecretReveal( return spec.EarlyDerivedSecretReveal(
revealed_index=revealed_index, revealed_index=revealed_index,
@ -60,15 +47,9 @@ def get_valid_custody_key_reveal(spec, state, period=None):
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, revealer_index) epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, revealer_index)
# Generate the secret that is being revealed # Generate the secret that is being revealed
reveal = bls_sign( domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign)
message_hash=hash_tree_root(spec.Epoch(epoch_to_sign)), signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain)
privkey=privkeys[revealer_index], reveal = bls.Sign(privkeys[revealer_index], signing_root)
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_RANDAO,
message_epoch=epoch_to_sign,
),
)
return spec.CustodyKeyReveal( return spec.CustodyKeyReveal(
revealer_index=revealer_index, revealer_index=revealer_index,
reveal=reveal, reveal=reveal,
@ -92,15 +73,9 @@ def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False)
responder_index) responder_index)
# Generate the responder key # Generate the responder key
responder_key = bls_sign( domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch)
message_hash=hash_tree_root(spec.Epoch(epoch)), signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain)
privkey=privkeys[responder_index], responder_key = bls.Sign(privkeys[responder_index], signing_root)
domain=spec.get_domain(
state=state,
domain_type=spec.DOMAIN_RANDAO,
message_epoch=epoch,
),
)
chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink) chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink)
@ -136,9 +111,9 @@ def get_valid_custody_response(spec, state, bit_challenge, custody_data, challen
chunk_index -= 1 chunk_index -= 1
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index]) chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
chunks_hash_tree_roots = [hash_tree_root(BytesN[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks] chunks_hash_tree_roots = [hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks]
chunks_hash_tree_roots += [ chunks_hash_tree_roots += [
hash_tree_root(BytesN[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK)) hash_tree_root(ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK))
for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))] for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))]
data_tree = get_merkle_tree(chunks_hash_tree_roots) data_tree = get_merkle_tree(chunks_hash_tree_roots)
@ -158,7 +133,7 @@ def get_valid_custody_response(spec, state, bit_challenge, custody_data, challen
return spec.CustodyResponse( return spec.CustodyResponse(
challenge_index=challenge_index, challenge_index=challenge_index,
chunk_index=chunk_index, chunk_index=chunk_index,
chunk=BytesN[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]), chunk=ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
data_branch=data_branch, data_branch=data_branch,
chunk_bits_branch=bitlist_chunk_branch, chunk_bits_branch=bitlist_chunk_branch,
chunk_bits_leaf=chunk_bits_leaf, chunk_bits_leaf=chunk_bits_leaf,

View File

@ -1,54 +1,46 @@
from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.test.helpers.keys import pubkeys, privkeys
from eth2spec.utils.bls import bls_sign from eth2spec.utils import bls
from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof
from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import List from eth2spec.utils.ssz.ssz_typing import List
def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=None, signed=False): def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=False):
deposit_data = spec.DepositData( deposit_data = spec.DepositData(
pubkey=pubkey, pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials, withdrawal_credentials=withdrawal_credentials,
amount=amount, amount=amount,
) )
if signed: if signed:
sign_deposit_data(spec, deposit_data, privkey, state) sign_deposit_data(spec, deposit_data, privkey)
return deposit_data return deposit_data
def sign_deposit_data(spec, deposit_data, privkey, state=None): def sign_deposit_data(spec, deposit_data, privkey):
if state is None:
# Genesis
domain = spec.compute_domain(spec.DOMAIN_DEPOSIT)
else:
domain = spec.get_domain(
state,
spec.DOMAIN_DEPOSIT,
)
deposit_message = spec.DepositMessage( deposit_message = spec.DepositMessage(
pubkey=deposit_data.pubkey, pubkey=deposit_data.pubkey,
withdrawal_credentials=deposit_data.withdrawal_credentials, withdrawal_credentials=deposit_data.withdrawal_credentials,
amount=deposit_data.amount) amount=deposit_data.amount)
signature = bls_sign( domain = spec.compute_domain(spec.DOMAIN_DEPOSIT)
message_hash=hash_tree_root(deposit_message), signing_root = spec.compute_signing_root(deposit_message, domain)
privkey=privkey, deposit_data.signature = bls.Sign(privkey, signing_root)
domain=domain,
)
deposit_data.signature = signature
def build_deposit(spec, def build_deposit(spec,
state,
deposit_data_list, deposit_data_list,
pubkey, pubkey,
privkey, privkey,
amount, amount,
withdrawal_credentials, withdrawal_credentials,
signed): signed):
deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=state, signed=signed) deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=signed)
index = len(deposit_data_list) index = len(deposit_data_list)
deposit_data_list.append(deposit_data) deposit_data_list.append(deposit_data)
return deposit_from_context(spec, deposit_data_list, index)
def deposit_from_context(spec, deposit_data_list, index):
deposit_data = deposit_data_list[index]
root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list)) root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list))
tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list]))
proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).to_bytes(32, 'little')] proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).to_bytes(32, 'little')]
@ -70,7 +62,6 @@ def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
deposit, root, deposit_data_list = build_deposit( deposit, root, deposit_data_list = build_deposit(
spec, spec,
None,
deposit_data_list, deposit_data_list,
pubkey, pubkey,
privkey, privkey,
@ -98,7 +89,6 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
deposit, root, deposit_data_list = build_deposit( deposit, root, deposit_data_list = build_deposit(
spec, spec,
state,
deposit_data_list, deposit_data_list,
pubkey, pubkey,
privkey, privkey,

View File

@ -1,6 +1,6 @@
from py_ecc import bls from py_ecc.bls import G2ProofOfPossession as bls
from eth2spec.phase0 import spec from eth2spec.phase0 import spec
privkeys = [i + 1 for i in range(spec.SLOTS_PER_EPOCH * 16)] privkeys = [i + 1 for i in range(spec.SLOTS_PER_EPOCH * 16)]
pubkeys = [bls.privtopub(privkey) for privkey in privkeys] pubkeys = [bls.PrivToPub(privkey) for privkey in privkeys]
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}

View File

@ -1,8 +1,5 @@
from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import ( from eth2spec.utils import bls
bls_aggregate_signatures,
bls_sign,
)
def sign_shard_attestation(spec, beacon_state, shard_state, block, participants): def sign_shard_attestation(spec, beacon_state, shard_state, block, participants):
@ -24,17 +21,10 @@ def sign_shard_attestation(spec, beacon_state, shard_state, block, participants)
privkey, privkey,
) )
) )
return bls.Aggregate(signatures)
return bls_aggregate_signatures(signatures)
def get_attestation_signature(spec, beacon_state, shard_state, message_hash, block_epoch, privkey): def get_attestation_signature(spec, beacon_state, shard_state, message_hash, block_epoch, privkey):
return bls_sign( domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_ATTESTER, block_epoch)
message_hash=message_hash, signing_root = spec.compute_signing_root(message_hash, domain)
privkey=privkey, return bls.Sign(privkey, signing_root)
domain=spec.get_domain(
state=beacon_state,
domain_type=spec.DOMAIN_SHARD_ATTESTER,
message_epoch=block_epoch,
)
)

View File

@ -1,10 +1,8 @@
from copy import deepcopy from copy import deepcopy
from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.keys import privkeys
from eth2spec.utils.bls import ( from eth2spec.utils import bls
bls_sign, from eth2spec.utils.bls import only_with_bls
only_with_bls,
)
from eth2spec.utils.ssz.ssz_impl import ( from eth2spec.utils.ssz.ssz_impl import (
hash_tree_root, hash_tree_root,
) )
@ -20,16 +18,9 @@ def sign_shard_block(spec, beacon_state, shard_state, block, proposer_index=None
proposer_index = spec.get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) proposer_index = spec.get_shard_proposer_index(beacon_state, shard_state.shard, block.slot)
privkey = privkeys[proposer_index] privkey = privkeys[proposer_index]
domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSER, spec.compute_epoch_of_shard_slot(block.slot))
block.signature = bls_sign( signing_root = spec.compute_signing_root(block, domain)
message_hash=hash_tree_root(block), block.signature = bls.Sign(privkey, signing_root)
privkey=privkey,
domain=spec.get_domain(
beacon_state,
spec.DOMAIN_SHARD_PROPOSER,
spec.compute_epoch_of_shard_slot(block.slot),
)
)
def build_empty_shard_block(spec, def build_empty_shard_block(spec,

View File

@ -0,0 +1,10 @@
from eth2spec.utils import bls
def sign_voluntary_exit(spec, state, voluntary_exit, privkey):
domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
signing_root = spec.compute_signing_root(voluntary_exit, domain)
return spec.SignedVoluntaryExit(
message=voluntary_exit,
signature=bls.Sign(privkey, signing_root)
)

View File

@ -3,9 +3,10 @@ from eth2spec.test.helpers.deposits import (
build_deposit, build_deposit,
prepare_state_and_deposit, prepare_state_and_deposit,
sign_deposit_data, sign_deposit_data,
) deposit_from_context)
from eth2spec.test.helpers.state import get_balance from eth2spec.test.helpers.state import get_balance
from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.utils import bls
def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True): def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True):
@ -93,6 +94,45 @@ def test_new_deposit_over_max(spec, state):
yield from run_deposit_processing(spec, state, deposit, validator_index) yield from run_deposit_processing(spec, state, deposit, validator_index)
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_sig_other_version(spec, state):
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
# Go through the effort of manually signing, not something normally done. This sig domain will be invalid.
deposit_message = spec.DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount)
domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=spec.Version('0xaabbccdd'))
deposit_data = spec.DepositData(
pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount,
signature=bls.Sign(privkey, spec.compute_signing_root(deposit_message, domain))
)
deposit, root, _ = deposit_from_context(spec, [deposit_data], 0)
state.eth1_deposit_index = 0
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count = 1
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=False)
@with_all_phases
@spec_state_test
@always_bls
def test_valid_sig_but_forked_state(spec, state):
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
# deposits will always be valid, regardless of the current fork
state.fork.current_version = spec.Version('0x1234abcd')
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True)
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
@always_bls @always_bls
@ -155,7 +195,6 @@ def test_wrong_deposit_for_deposit_count(spec, state):
privkey_1 = privkeys[index_1] privkey_1 = privkeys[index_1]
_, _, deposit_data_leaves = build_deposit( _, _, deposit_data_leaves = build_deposit(
spec, spec,
state,
deposit_data_leaves, deposit_data_leaves,
pubkey_1, pubkey_1,
privkey_1, privkey_1,
@ -171,7 +210,6 @@ def test_wrong_deposit_for_deposit_count(spec, state):
privkey_2 = privkeys[index_2] privkey_2 = privkeys[index_2]
deposit_2, root_2, deposit_data_leaves = build_deposit( deposit_2, root_2, deposit_data_leaves = build_deposit(
spec, spec,
state,
deposit_data_leaves, deposit_data_leaves,
pubkey_2, pubkey_2,
privkey_2, privkey_2,
@ -197,6 +235,6 @@ def test_bad_merkle_proof(spec, state):
# mess up merkle branch # mess up merkle branch
deposit.proof[5] = spec.Bytes32() deposit.proof[5] = spec.Bytes32()
sign_deposit_data(spec, deposit.data, privkeys[validator_index], state=state) sign_deposit_data(spec, deposit.data, privkeys[validator_index])
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False) yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False)

Some files were not shown because too many files have changed in this diff Show More