diff --git a/.circleci/config.yml b/.circleci/config.yml index 17db49b3a9..1de55179d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,19 +35,19 @@ commands: description: "Restore the cache with pyspec keys" steps: - restore_cached_venv: - venv_name: v25-pyspec + venv_name: v30-pyspec reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "requirements_preinstallation.txt" }} save_pyspec_cached_venv: description: Save a venv into a cache with pyspec keys" steps: - save_cached_venv: - venv_name: v25-pyspec + venv_name: v30-pyspec reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "requirements_preinstallation.txt" }} venv_path: ./venv jobs: checkout_specs: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: # Restore git repo at point close to target branch/revision, to speed up checkout @@ -67,7 +67,7 @@ jobs: - ~/specs-repo install_pyspec_test: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -75,11 +75,11 @@ jobs: - restore_pyspec_cached_venv - run: name: Install pyspec requirements - command: make install_test + command: make eth2spec - save_pyspec_cached_venv test-phase0: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -87,12 +87,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=phase0 + command: make test fork=phase0 - store_test_results: path: tests/core/pyspec/test-reports test-altair: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -100,12 +100,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=altair + command: make test fork=altair - store_test_results: path: tests/core/pyspec/test-reports test-bellatrix: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -113,12 +113,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=bellatrix + command: make test fork=bellatrix - store_test_results: path: tests/core/pyspec/test-reports test-capella: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -126,12 +126,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=capella + command: make test fork=capella - store_test_results: path: tests/core/pyspec/test-reports test-deneb: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -139,12 +139,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=deneb + command: make test fork=deneb - store_test_results: path: tests/core/pyspec/test-reports test-electra: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -152,12 +152,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=electra + command: make test fork=electra - store_test_results: path: tests/core/pyspec/test-reports - test-whisk: + test-fulu: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -165,12 +165,12 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=whisk + command: make test fork=fulu - store_test_results: path: tests/core/pyspec/test-reports - test-eip7594: + test-whisk: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: @@ -178,41 +178,23 @@ jobs: - restore_pyspec_cached_venv - run: name: Run py-tests - command: make citest fork=eip7594 + command: make test fork=whisk - store_test_results: path: tests/core/pyspec/test-reports - table_of_contents: - docker: - - image: circleci/node:10.16.3 - working_directory: ~/specs-repo - steps: - - checkout - - run: - name: Check table of contents - command: sudo npm install -g doctoc@2.2.0 && make check_toc - codespell: - docker: - - image: cimg/python:3.12.4 - working_directory: ~/specs-repo - steps: - - checkout - - run: - name: Check codespell - command: pip install 'codespell<3.0.0,>=2.0.0' --user && make codespell lint: docker: - - image: cimg/python:3.12.4 + - image: cimg/python:3.12-node working_directory: ~/specs-repo steps: - restore_cache: key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - restore_pyspec_cached_venv + - run: + name: Install doctoc + command: sudo npm install -g doctoc@2.2.0 - run: name: Run linter for pyspec command: make lint - - run: - name: Run linter for test generators - command: make lint_generators workflows: version: 2.1 test_spec: @@ -239,14 +221,12 @@ workflows: - test-electra: requires: - install_pyspec_test - - test-whisk: + - test-fulu: requires: - install_pyspec_test - - test-eip7594: + - test-whisk: requires: - install_pyspec_test - - table_of_contents - - codespell - lint: requires: - install_pyspec_test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 209bb4a800..930781e77c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,7 @@ name: Publish docs on: push: branches: - - master + - master permissions: contents: write jobs: @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Build docs - run: make copy_docs + run: make _copy_docs - uses: actions/setup-python@v4 with: python-version: 3.x diff --git a/.github/workflows/generate_vectors.yml b/.github/workflows/generate_vectors.yml index 1f14ff9158..128d402776 100644 --- a/.github/workflows/generate_vectors.yml +++ b/.github/workflows/generate_vectors.yml @@ -36,19 +36,10 @@ jobs: with: python-version: '3.12.4' cache: '' - - name: Clean up Spec Repository - run: | - cd consensus-specs - make clean - - name: Install dependencies and generate pyspec - run: | - cd consensus-specs - make install_test - make -B pyspec - name: Generate tests run: | cd consensus-specs - make -j 16 generate_tests 2>&1 | tee ../consensustestgen.log + make -j 16 gen_all 2>&1 | tee ../consensustestgen.log cp -r presets/ ../consensus-spec-tests/presets cp -r configs/ ../consensus-spec-tests/configs find . -type d -empty -delete diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b8c2fa6e14..3f10d19668 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -24,9 +24,9 @@ on: - cron: '0 0 * * *' jobs: - table_of_contents: - runs-on: [self-hosted-ghr-custom, size-s-x64, profile-consensusSpecs] - steps: + lint: + runs-on: [self-hosted-ghr-custom, size-l-x64, profile-consensusSpecs] + steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js @@ -34,47 +34,34 @@ jobs: with: node-version: '20' cache: '' - - name: Check table of contents - run: npm install -g doctoc@2.2.0 && make check_toc - - codespell: - runs-on: [self-hosted-ghr-custom, size-s-x64, profile-consensusSpecs] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12.4' cache: '' - - name: Check codespell - run: make codespell + - name: Install doctoc + run: npm install -g doctoc@2.2.0 + - name: Run linter for pyspec + run: make lint - lint: + whitespace: runs-on: [self-hosted-ghr-custom, size-l-x64, profile-consensusSpecs] steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup Rust for dependencies - uses: actions-rust-lang/setup-rust-toolchain@v1 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.12.4' - cache: '' - - name: Install pyspec requirements - run: make install_test - - name: Run linter for pyspec - run: make lint - - name: Run linter for test generators - run: make lint_generators + - name: Check for trailing whitespace + run: | + if git grep -n '[[:blank:]]$'; then + echo "Trailing whitespace found. Please fix it." + exit 1 + fi pyspec-tests: runs-on: [self-hosted-ghr-custom, size-xl-x64, profile-consensusSpecs] - needs: [lint,codespell,table_of_contents] + needs: [lint] strategy: matrix: - version: ["phase0", "altair", "bellatrix", "capella", "deneb", "electra", "whisk", "eip7594"] + version: ["phase0", "altair", "bellatrix", "capella", "deneb", "electra", "fulu", "whisk"] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -85,26 +72,24 @@ jobs: with: python-version: '3.12.4' cache: '' - - name: set TEST_PRESET_TYPE + - name: set preset if: github.event.inputs.test_preset_type != '' run: | echo "spec_test_preset_type=${{ github.event.inputs.test_preset_type || env.TEST_PRESET_TYPE }}" >> $GITHUB_ENV - - name: set TEST_PRESET_TYPE + - name: set preset if: ${{ (github.event_name == 'push' && github.ref_name != 'master') || github.event_name == 'pull_request' }} run: | - echo "spec_test_preset_type=${{ env.TEST_PRESET_TYPE}}" >> $GITHUB_ENV - - name: set TEST_PRESET_TYPE + echo "spec_test_preset_type=${{ env.TEST_PRESET_TYPE }}" >> $GITHUB_ENV + - name: set preset if: ${{ github.event_name == 'push' && github.ref_name == 'master' }} run: | echo "spec_test_preset_type=mainnet" >> $GITHUB_ENV - - name: set TEST_PRESET_TYPE + - name: set preset if: github.event.schedule=='0 0 * * *' run: | echo "spec_test_preset_type=mainnet" >> $GITHUB_ENV - - name: Install pyspec requirements - run: make install_test - name: test-${{ matrix.version }} - run: make citest fork=${{ matrix.version }} TEST_PRESET_TYPE=${{env.spec_test_preset_type}} + run: make test fork=${{ matrix.version }} preset=${{ env.spec_test_preset_type }} - uses: actions/upload-artifact@v4 if: always() with: @@ -121,7 +106,11 @@ jobs: with: python-version: '3.12.4' cache: '' - - name: Install pyspec requirements - run: make install_test - name: Run generators with --modcheck - run: make generate_tests modcheck=true + run: make gen_all modcheck=true 2>&1 | tee consensustestgen.log + - name: Check for errors + run: | + if grep -q "\[ERROR\]" consensustestgen.log; then + echo "There is an error in the log" + exit 1 + fi diff --git a/.gitignore b/.gitignore index ad62aa177e..56f84dafd8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ venv .venv /.pytest_cache *.swp +.eth2spec build/ output/ @@ -22,8 +23,8 @@ tests/core/pyspec/eth2spec/bellatrix/ tests/core/pyspec/eth2spec/capella/ tests/core/pyspec/eth2spec/deneb/ tests/core/pyspec/eth2spec/electra/ +tests/core/pyspec/eth2spec/fulu/ tests/core/pyspec/eth2spec/whisk/ -tests/core/pyspec/eth2spec/eip7594/ tests/core/pyspec/eth2spec/eip6800/ tests/core/pyspec/eth2spec/eip7732/ diff --git a/Makefile b/Makefile index 7a3ebbf6c6..a3a3e24288 100644 --- a/Makefile +++ b/Makefile @@ -1,134 +1,198 @@ -SPEC_DIR = ./specs -SSZ_DIR = ./ssz -TEST_LIBS_DIR = ./tests/core -TEST_GENERATORS_DIR = ./tests/generators -# The working dir during testing -PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec -ETH2SPEC_MODULE_DIR = $(PY_SPEC_DIR)/eth2spec -TEST_REPORT_DIR = $(PY_SPEC_DIR)/test-reports -TEST_VECTOR_DIR = ../consensus-spec-tests/tests -GENERATOR_DIR = ./tests/generators -CONFIGS_DIR = ./configs +all: help + +# A list of executable specifications. +# These must pass a strict linter. +ALL_EXECUTABLE_SPEC_NAMES = \ + phase0 \ + altair \ + bellatrix \ + capella \ + deneb \ + electra \ + fulu \ + whisk \ + eip6800 \ + eip7732 + +# A list of fake targets. +.PHONY: \ + check_toc \ + clean \ + coverage \ + detect_errors \ + eth2spec \ + gen_all \ + gen_list \ + help \ + kzg_setups \ + lint \ + pyspec \ + serve_docs \ + test + +############################################################################### +# Help +############################################################################### + +BOLD = $(shell tput bold) +NORM = $(shell tput sgr0) + +# Print target descriptions. +help: + @echo "make $(BOLD)check_toc$(NORM) -- check table of contents" + @echo "make $(BOLD)clean$(NORM) -- delete all untracked files" + @echo "make $(BOLD)coverage$(NORM) -- run pyspec tests with coverage" + @echo "make $(BOLD)detect_errors$(NORM) -- detect generator errors" + @echo "make $(BOLD)eth2spec$(NORM) -- force rebuild eth2spec package" + @echo "make $(BOLD)gen_$(NORM) -- run a single generator" + @echo "make $(BOLD)gen_all$(NORM) -- run all generators" + @echo "make $(BOLD)gen_list$(NORM) -- list all generator targets" + @echo "make $(BOLD)kzg_setups$(NORM) -- generate trusted setups" + @echo "make $(BOLD)lint$(NORM) -- run the linters" + @echo "make $(BOLD)pyspec$(NORM) -- generate python specifications" + @echo "make $(BOLD)serve_docs$(NORM) -- start a local docs web server" + @echo "make $(BOLD)test$(NORM) -- run pyspec tests" + +############################################################################### +# Virtual Environment +############################################################################### + +VENV = venv +PYTHON_VENV = $(VENV)/bin/python3 +PIP_VENV = $(VENV)/bin/pip3 +CODESPELL_VENV = $(VENV)/bin/codespell + +# Make a virtual environment will all of the necessary dependencies. +$(VENV): requirements_preinstallation.txt + @echo "Creating virtual environment" + @python3 -m venv $(VENV) + @$(PIP_VENV) install -r requirements_preinstallation.txt + +############################################################################### +# Specification +############################################################################### + +TEST_LIBS_DIR = $(CURDIR)/tests/core +PYSPEC_DIR = $(TEST_LIBS_DIR)/pyspec +SITE_PACKAGES := $(wildcard $(VENV)/lib/python*/site-packages) +ETH2SPEC := $(SITE_PACKAGES)/eth2spec + +# Install the eth2spec package. +# The pipe indicates that venv is an order-only prerequisite. +# When restoring venv cache, its timestamp is newer than eth2spec. +$(ETH2SPEC): setup.py | $(VENV) + @$(PIP_VENV) install .[docs,lint,test,generator] + +# Force rebuild/install the eth2spec package. +eth2spec: + $(MAKE) --always-make $(ETH2SPEC) + +# Create the pyspec for all phases. +pyspec: $(VENV) setup.py + @echo "Building all pyspecs" + @$(PYTHON_VENV) setup.py pyspecdev + +############################################################################### +# Testing +############################################################################### + +TEST_REPORT_DIR = $(PYSPEC_DIR)/test-reports + +# Run pyspec tests. +# To run a specific test, append k=, eg: +# make test k=test_verify_kzg_proof +# To run tests for a specific fork, append fork=, eg: +# make test fork=deneb +# To run tests for a specific preset, append preset=, eg: +# make test preset=mainnet +# Or all at the same time, eg: +# make test preset=mainnet fork=deneb k=test_verify_kzg_proof +# To run tests with a specific bls library, append bls=, eg: +# make test bls=arkworks +test: MAYBE_TEST := $(if $(k),-k=$(k)) +test: MAYBE_FORK := $(if $(fork),--fork=$(fork)) +test: PRESET := --preset=$(if $(preset),$(preset),minimal) +test: BLS := --bls-type=$(if $(bls),$(bls),fastest) +test: $(ETH2SPEC) pyspec + @mkdir -p $(TEST_REPORT_DIR) + @$(PYTHON_VENV) -m pytest \ + -n auto \ + $(MAYBE_TEST) \ + $(MAYBE_FORK) \ + $(PRESET) \ + $(BLS) \ + --junitxml=$(TEST_REPORT_DIR)/test_results.xml \ + $(PYSPEC_DIR)/eth2spec + +############################################################################### +# Coverage +############################################################################### + TEST_PRESET_TYPE ?= minimal -# Collect a list of generator names -GENERATORS = $(sort $(dir $(wildcard $(GENERATOR_DIR)/*/.))) -# Map this list of generator paths to "gen_{generator name}" entries -GENERATOR_TARGETS = $(patsubst $(GENERATOR_DIR)/%/, gen_%, $(GENERATORS)) -GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENERATORS)) -# Documents +COV_HTML_OUT=$(PYSPEC_DIR)/.htmlcov +COV_INDEX_FILE=$(COV_HTML_OUT)/index.html +COVERAGE_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), --cov=eth2spec.$S.$(TEST_PRESET_TYPE)) + +# Run pytest with coverage tracking +_test_with_coverage: MAYBE_TEST := $(if $(k),-k=$(k)) +_test_with_coverage: MAYBE_FORK := $(if $(fork),--fork=$(fork)) +_test_with_coverage: $(ETH2SPEC) pyspec + @$(PYTHON_VENV) -m pytest \ + -n auto \ + $(MAYBE_TEST) \ + $(MAYBE_FORK) \ + --disable-bls \ + $(COVERAGE_SCOPE) \ + --cov-report="html:$(COV_HTML_OUT)" \ + --cov-branch \ + $(PYSPEC_DIR)/eth2spec + +# Run tests with coverage then open the coverage report. +# See `make test` for a list of options. +coverage: _test_with_coverage + @echo "Opening result: $(COV_INDEX_FILE)" + @((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) & + +############################################################################### +# Documentation +############################################################################### + DOCS_DIR = ./docs +FORK_CHOICE_DIR = ./fork_choice +SPEC_DIR = ./specs SSZ_DIR = ./ssz SYNC_DIR = ./sync -FORK_CHOICE_DIR = ./fork_choice -# To check generator matching: -#$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}]) +# Copy files to the docs directory. +_copy_docs: + @cp -r $(SPEC_DIR) $(DOCS_DIR) + @cp -r $(SYNC_DIR) $(DOCS_DIR) + @cp -r $(SSZ_DIR) $(DOCS_DIR) + @cp -r $(FORK_CHOICE_DIR) $(DOCS_DIR) + @cp $(CURDIR)/README.md $(DOCS_DIR)/README.md +# Start a local documentation server. +serve_docs: _copy_docs + @mkdocs build + @mkdocs serve + +############################################################################### +# Checks +############################################################################### + +FLAKE8_CONFIG = $(CURDIR)/flake8.ini +MYPY_CONFIG = $(CURDIR)/mypy.ini +PYLINT_CONFIG = $(CURDIR)/pylint.ini + +PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), $(PYSPEC_DIR)/eth2spec/$S) +MYPY_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), -p eth2spec.$S) +TEST_GENERATORS_DIR = ./tests/generators MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/*/*.md) \ $(wildcard $(SPEC_DIR)/*/*/*.md) \ $(wildcard $(SPEC_DIR)/_features/*/*.md) \ $(wildcard $(SPEC_DIR)/_features/*/*/*.md) \ $(wildcard $(SSZ_DIR)/*.md) -ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk eip6800 eip7594 eip7732 -# The parameters for commands. Use `foreach` to avoid listing specs again. -COVERAGE_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), --cov=eth2spec.$S.$(TEST_PRESET_TYPE)) -PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), ./eth2spec/$S) -MYPY_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), -p eth2spec.$S) - -COV_HTML_OUT=.htmlcov -COV_HTML_OUT_DIR=$(PY_SPEC_DIR)/$(COV_HTML_OUT) -COV_INDEX_FILE=$(COV_HTML_OUT_DIR)/index.html - -CURRENT_DIR = ${CURDIR} -GENERATOR_ERROR_LOG_FILE = $(CURRENT_DIR)/$(TEST_VECTOR_DIR)/testgen_error_log.txt - -SCRIPTS_DIR = ${CURRENT_DIR}/scripts - -.PHONY: clean partial_clean all test citest lint generate_tests pyspec install_test open_cov \ - check_toc detect_generator_incomplete detect_generator_error_log - -all: $(PY_SPEC_ALL_TARGETS) - -# deletes everything except the venvs -partial_clean: - rm -rf $(TEST_VECTOR_DIR) - rm -rf $(GENERATOR_VENVS) - rm -rf .pytest_cache - rm -f .coverage - rm -rf $(PY_SPEC_DIR)/.pytest_cache - rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/.pytest_cache - rm -rf $(COV_HTML_OUT_DIR) - rm -rf $(TEST_REPORT_DIR) - rm -rf eth2spec.egg-info dist build - rm -rf build; - @for spec_name in $(ALL_EXECUTABLE_SPEC_NAMES) ; do \ - echo $$spec_name; \ - rm -rf $(ETH2SPEC_MODULE_DIR)/$$spec_name; \ - done - -clean: partial_clean - rm -rf venv - # legacy cleanup. The pyspec venv should be located at the repository root - rm -rf $(PY_SPEC_DIR)/venv - rm -rf $(DEPOSIT_CONTRACT_COMPILER_DIR)/venv - rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/venv - -# The pyspec is rebuilt to enforce the /specs being part of eth2specs source distribution. It could be forgotten otherwise. -dist_build: pyspec - python3 setup.py sdist bdist_wheel - -dist_check: - python3 -m twine check dist/* - -dist_upload: - python3 -m twine upload dist/* - -build_wheel: install_test pyspec - . venv/bin/activate && \ - python3 -m build --no-isolation --outdir ./dist ./ - -# "make generate_tests" to run all generators -generate_tests: $(GENERATOR_TARGETS) - -# "make pyspec" to create the pyspec for all phases. -pyspec: - @python3 -m venv venv; . venv/bin/activate; python3 setup.py pyspecdev - -# check the setup tool requirements -preinstallation: - python3 -m venv venv && \ - . venv/bin/activate && \ - python3 -m pip install -r requirements_preinstallation.txt - -install_test: preinstallation - . venv/bin/activate && \ - python3 -m pip install -e .[lint,test] - -# Testing against `minimal` or `mainnet` config by default -test: pyspec - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n auto --disable-bls $(COVERAGE_SCOPE) --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec - -# Testing against `minimal` or `mainnet` config by default -find_test: pyspec - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -k=$(K) --disable-bls $(COVERAGE_SCOPE) --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec - -citest: pyspec - mkdir -p $(TEST_REPORT_DIR); -ifdef fork - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n auto --bls-type=fastest --preset=$(TEST_PRESET_TYPE) --fork=$(fork) --junitxml=test-reports/test_results.xml eth2spec -else - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n auto --bls-type=fastest --preset=$(TEST_PRESET_TYPE) --junitxml=test-reports/test_results.xml eth2spec -endif - - -open_cov: - ((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) & - # Check all files and error if any ToC were modified. check_toc: $(MARKDOWN_FILES:=.toc) @[ "$$(find . -name '*.md.tmp' -print -quit)" ] && exit 1 || exit 0 @@ -145,86 +209,67 @@ check_toc: $(MARKDOWN_FILES:=.toc) echo "\033[1;34m See $*.tmp\033[0m"; \ fi -codespell: - codespell . --skip "./.git,./venv,$(PY_SPEC_DIR)/.mypy_cache" -I .codespell-whitelist - -lint: pyspec - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --config $(CURRENT_DIR)/flake8.ini ./eth2spec \ - && python -m pylint --rcfile $(CURRENT_DIR)/pylint.ini $(PYLINT_SCOPE) \ - && python -m mypy --config-file $(CURRENT_DIR)/mypy.ini $(MYPY_SCOPE) - -lint_generators: pyspec - . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ - flake8 --config $(CURRENT_DIR)/flake8.ini - -# If set to true, it will not run generator tests. -modcheck ?= false - -# Runs a generator, identified by param 1 -define run_generator - # Started! - # Create output directory - # Navigate to the generator - # Create a virtual environment, if it does not exist already - # Activate the venv, this is where dependencies are installed for the generator - # Install all the necessary requirements - # Run the generator. The generator is assumed to have an "main.py" file. - # We output to the tests dir (generator program should accept a "-o " argument. - # `-l minimal general` can be added to the generator call to filter to smaller configs, when testing. - echo "generator $(1) started"; \ - mkdir -p $(TEST_VECTOR_DIR); \ - cd $(GENERATOR_DIR)/$(1); \ - if ! test -d venv; then python3 -m venv venv; fi; \ - . venv/bin/activate; \ - pip3 install ../../../dist/eth2spec-*.whl; \ - pip3 install 'eth2spec[generator]'; \ - python3 main.py -o $(CURRENT_DIR)/$(TEST_VECTOR_DIR) $(if $(filter true,$(modcheck)),--modcheck); \ - echo "generator $(1) finished" -endef - -# The tests dir itself is simply built by creating the directory (recursively creating deeper directories if necessary) -$(TEST_VECTOR_DIR): - $(info creating test output directory, for generators: ${GENERATOR_TARGETS}) - mkdir -p $@ -$(TEST_VECTOR_DIR)/: - $(info ignoring duplicate tests dir) - -gen_kzg_setups: - cd $(SCRIPTS_DIR); \ - if ! test -d venv; then python3 -m venv venv; fi; \ - . venv/bin/activate; \ - pip3 install -r requirements.txt; \ - python3 ./gen_kzg_trusted_setups.py --secret=1337 --g1-length=4096 --g2-length=65 --output-dir ${CURRENT_DIR}/presets/minimal/trusted_setups; \ - python3 ./gen_kzg_trusted_setups.py --secret=1337 --g1-length=4096 --g2-length=65 --output-dir ${CURRENT_DIR}/presets/mainnet/trusted_setups - -# For any generator, build it using the run_generator function. -# (creation of output dir is a dependency) -gen_%: build_wheel $(TEST_VECTOR_DIR) - $(call run_generator,$*) - -detect_generator_incomplete: $(TEST_VECTOR_DIR) - find $(TEST_VECTOR_DIR) -name "INCOMPLETE" - -detect_generator_error_log: $(TEST_VECTOR_DIR) - [ -f $(GENERATOR_ERROR_LOG_FILE) ] && echo "[ERROR] $(GENERATOR_ERROR_LOG_FILE) file exists" || echo "[PASSED] error log file does not exist" - - -# For docs reader -install_docs: - python3 -m venv venv; . venv/bin/activate; python3 -m pip install -e .[docs]; - -copy_docs: - cp -r $(SPEC_DIR) $(DOCS_DIR); - cp -r $(SYNC_DIR) $(DOCS_DIR); - cp -r $(SSZ_DIR) $(DOCS_DIR); - cp -r $(FORK_CHOICE_DIR) $(DOCS_DIR); - cp $(CURRENT_DIR)/README.md $(DOCS_DIR)/README.md - -build_docs: copy_docs - . venv/bin/activate; - mkdocs build - -serve_docs: - . venv/bin/activate; - mkdocs serve +# Check for mistakes. +lint: $(ETH2SPEC) pyspec check_toc + @$(CODESPELL_VENV) . --skip "./.git,$(VENV),$(PYSPEC_DIR)/.mypy_cache" -I .codespell-whitelist + @$(PYTHON_VENV) -m flake8 --config $(FLAKE8_CONFIG) $(PYSPEC_DIR)/eth2spec + @$(PYTHON_VENV) -m flake8 --config $(FLAKE8_CONFIG) $(TEST_GENERATORS_DIR) + @$(PYTHON_VENV) -m pylint --rcfile $(PYLINT_CONFIG) $(PYLINT_SCOPE) + @$(PYTHON_VENV) -m mypy --config-file $(MYPY_CONFIG) $(MYPY_SCOPE) + +############################################################################### +# Generators +############################################################################### + +TEST_VECTOR_DIR = $(CURDIR)/../consensus-spec-tests/tests +GENERATOR_DIR = $(CURDIR)/tests/generators +SCRIPTS_DIR = $(CURDIR)/scripts +GENERATOR_ERROR_LOG_FILE = $(TEST_VECTOR_DIR)/testgen_error_log.txt +GENERATORS = $(sort $(dir $(wildcard $(GENERATOR_DIR)/*/.))) +GENERATOR_TARGETS = $(patsubst $(GENERATOR_DIR)/%/, gen_%, $(GENERATORS)) + +# List available generators. +gen_list: + @for target in $(shell echo $(GENERATOR_TARGETS) | tr ' ' '\n' | sort -n); do \ + echo $$target; \ + done + +# Run one generator. +# To check modules for a generator, append modcheck=true, eg: +# make gen_genesis modcheck=true +gen_%: MAYBE_MODCHECK := $(if $(filter true,$(modcheck)),--modcheck) +gen_%: $(ETH2SPEC) pyspec + @mkdir -p $(TEST_VECTOR_DIR) + @$(PYTHON_VENV) $(GENERATOR_DIR)/$*/main.py \ + --output $(TEST_VECTOR_DIR) \ + $(MAYBE_MODCHECK) + +# Run all generators then check for errors. +gen_all: $(GENERATOR_TARGETS) detect_errors + +# Detect errors in generators. +detect_errors: $(TEST_VECTOR_DIR) + @find $(TEST_VECTOR_DIR) -name "INCOMPLETE" + @if [ -f $(GENERATOR_ERROR_LOG_FILE) ]; then \ + echo "[ERROR] $(GENERATOR_ERROR_LOG_FILE) file exists"; \ + else \ + echo "[PASSED] error log file does not exist"; \ + fi + +# Generate KZG trusted setups for testing. +kzg_setups: $(ETH2SPEC) + @for preset in minimal mainnet; do \ + $(PYTHON_VENV) $(SCRIPTS_DIR)/gen_kzg_trusted_setups.py \ + --secret=1337 \ + --g1-length=4096 \ + --g2-length=65 \ + --output-dir $(CURDIR)/presets/$$preset/trusted_setups; \ + done + +############################################################################### +# Cleaning +############################################################################### + +# Delete all untracked files. +clean: + @git clean -fdx \ No newline at end of file diff --git a/README.md b/README.md index c3a57c6256..e58927ac9e 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Features are researched and developed in parallel, and then consolidated into se | Seq. | Code Name | Fork Epoch | Specs | | - | - | - | - | | 5 | **Electra** | TBD |
  • Core
    • [Beacon Chain changes](specs/electra/beacon-chain.md)
    • [Electra fork](specs/electra/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/electra/light-client/sync-protocol.md) ([fork](specs/electra/light-client/fork.md), [networking](specs/electra/light-client/p2p-interface.md))
    • [Honest validator guide changes](specs/electra/validator.md)
    • [P2P networking](specs/electra/p2p-interface.md)
| +| 6 | **Fulu** | TBD |
  • Core
    • [Beacon Chain changes](specs/fulu/beacon-chain.md)
    • [Fulu fork](specs/fulu/fork.md)
    • [Data availability sampling core](specs/fulu/das-core.md)
    • [Polynomial commitments sampling](specs/fulu/polynomial-commitments-sampling.md)
    • [Fork choice changes](specs/fulu/fork-choice.md)
  • Additions
    • [P2P networking](specs/fulu/p2p-interface.md)
    • [Peer sampling](specs/fulu/peer-sampling.md)
| ### Outdated Specifications @@ -87,8 +88,7 @@ The consensus-specs repo can be used by running the tests locally or inside a do To run the tests locally: - Clone the repository with `git clone https://github.com/ethereum/consensus-specs.git` - Switch to the directory `cd consensus-specs` -- Install the dependencies with: `make install_test && make preinstallation && make pyspec` -- Run the tests with `make citest` +- Run the tests with `make test` To run the tests inside a docker container: - Switch to the directory with `cd scripts` diff --git a/configs/README.md b/configs/README.md index 6ef081e4c4..beb04a11f6 100644 --- a/configs/README.md +++ b/configs/README.md @@ -1,7 +1,7 @@ # Configurations This directory contains a set of configurations used for testing, testnets, and mainnet. -A client binary may be compiled for a specific `PRESET_BASE`, +A client binary may be compiled for a specific `PRESET_BASE`, and then load different configurations around that preset to participate in different networks or tests. Standard configs: @@ -24,7 +24,7 @@ In this case, the suffix on the new variable may be removed, and the old variabl A previous iteration of forking made use of "timelines", but this collides with the definitions used in the spec (variables for special forking slots, etc.), and was not integrated sufficiently in any of the spec tools or implementations. Instead, the config essentially doubles as fork definition now, e.g. changing the value for `ALTAIR_FORK_EPOCH` changes the fork. - + ## Format Each preset and configuration is a key-value mapping. diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 36b4db4123..deb3dcf5fe 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -53,9 +53,9 @@ DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC # Electra ELECTRA_FORK_VERSION: 0x05000000 ELECTRA_FORK_EPOCH: 18446744073709551615 # temporary stub -# EIP7594 -EIP7594_FORK_VERSION: 0x06000000 # temporary stub -EIP7594_FORK_EPOCH: 18446744073709551615 +# Fulu +FULU_FORK_VERSION: 0x06000000 +FULU_FORK_EPOCH: 18446744073709551615 # temporary stub # WHISK WHISK_FORK_VERSION: 0x08000000 # temporary stub WHISK_FORK_EPOCH: 18446744073709551615 @@ -144,35 +144,43 @@ ATTESTATION_SUBNET_PREFIX_BITS: 6 # Deneb # `2**7` (=128) MAX_REQUEST_BLOCKS_DENEB: 128 -# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK -MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 -## `uint64(6)` +# `uint64(6)` MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 -# Whisk -# `Epoch(2**8)` -WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 -# `Epoch(2)` -WHISK_PROPOSER_SELECTION_GAP: 2 - -# EIP7594 +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9 (= 256,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Fulu NUMBER_OF_COLUMNS: 128 +NUMBER_OF_CUSTODY_GROUPS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 SAMPLES_PER_SLOT: 8 CUSTODY_REQUIREMENT: 4 -BLOB_SIDECAR_SUBNET_COUNT_EIP7594: 8 -MAX_BLOBS_PER_BLOCK_EIP7594: 8 -# `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_EIP7594` -MAX_REQUEST_BLOB_SIDECARS_EIP7594: 1024 - -# [New in Electra:EIP7251] -MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) +MAX_BLOBS_PER_BLOCK_FULU: 12 +# `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_FULU` +MAX_REQUEST_BLOB_SIDECARS_FULU: 1536 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 # EIP7732 MAX_REQUEST_PAYLOADS: 128 diff --git a/configs/minimal.yaml b/configs/minimal.yaml index ae19518af7..460474ebf7 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -52,9 +52,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # Electra ELECTRA_FORK_VERSION: 0x05000001 ELECTRA_FORK_EPOCH: 18446744073709551615 -# EIP7594 -EIP7594_FORK_VERSION: 0x06000001 -EIP7594_FORK_EPOCH: 18446744073709551615 +# Fulu +FULU_FORK_VERSION: 0x06000001 +FULU_FORK_EPOCH: 18446744073709551615 # WHISK WHISK_FORK_VERSION: 0x08000001 WHISK_FORK_EPOCH: 18446744073709551615 @@ -145,33 +145,41 @@ ATTESTATION_SUBNET_PREFIX_BITS: 6 # Deneb # `2**7` (=128) MAX_REQUEST_BLOCKS_DENEB: 128 -# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK -MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 ## `uint64(6)` MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 -# Whisk -WHISK_EPOCHS_PER_SHUFFLING_PHASE: 4 -WHISK_PROPOSER_SELECTION_GAP: 1 - -# EIP7594 +# Electra +# [customized] 2**6 * 10**9 (= 64,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 +# [customized] 2**7 * 10**9 (= 128,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Fulu NUMBER_OF_COLUMNS: 128 +NUMBER_OF_CUSTODY_GROUPS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 SAMPLES_PER_SLOT: 8 CUSTODY_REQUIREMENT: 4 -BLOB_SIDECAR_SUBNET_COUNT_EIP7594: 8 -MAX_BLOBS_PER_BLOCK_EIP7594: 8 -# `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_EIP7594` -MAX_REQUEST_BLOB_SIDECARS_EIP7594: 1024 - -# [New in Electra:EIP7251] -MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) +MAX_BLOBS_PER_BLOCK_FULU: 12 +# `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_FULU` +MAX_REQUEST_BLOB_SIDECARS_FULU: 1536 + +# Whisk +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 4 +WHISK_PROPOSER_SELECTION_GAP: 1 # EIP7732 MAX_REQUEST_PAYLOADS: 128 diff --git a/docker/Dockerfile b/docker/Dockerfile index 8ec384499d..d4b2d3567a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,7 +16,4 @@ RUN apt update && ${INSTALL_CMD} && ${PIP_UPGRADE_CMD} && rm -rf /var/lib/apt/li COPY . . # Inline installation commands -RUN make install_test && \ - make preinstallation && \ - make pyspec - +RUN make pyspec diff --git a/docker/README.md b/docker/README.md index 44dc2b95e5..4824fc283a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -6,7 +6,7 @@ This dockerfile sets up the dependencies required to run consensus-spec tests. T Handy commands: - `docker run -it $IMAGE_NAME /bin/sh` will give you a shell inside the docker container to manually run any tests -- `docker run $IMAGE_NAME make citest` will run the make citest command inside the docker container +- `docker run $IMAGE_NAME make test` will run the make test command inside the docker container Ideally manual running of docker containers is for advanced users, we recommend the script based approach described below for most users. diff --git a/docs/docs/templates/beacon-chain-template.md b/docs/docs/templates/beacon-chain-template.md index 4d22d3908e..02e95d4c4f 100644 --- a/docs/docs/templates/beacon-chain-template.md +++ b/docs/docs/templates/beacon-chain-template.md @@ -66,19 +66,3 @@ class CONTAINER_NAME(Container): ### Block processing - - - - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure testing only. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - ... -``` diff --git a/mkdocs.yml b/mkdocs.yml index dc6b352baa..3a9d76f8c1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,7 +7,7 @@ theme: - scheme: default primary: black toggle: - icon: material/brightness-7 + icon: material/brightness-7 name: Switch to dark mode - scheme: slate primary: black diff --git a/presets/mainnet/eip6800.yaml b/presets/mainnet/eip6800.yaml index d74ee62122..2282d8f960 100644 --- a/presets/mainnet/eip6800.yaml +++ b/presets/mainnet/eip6800.yaml @@ -2,7 +2,7 @@ # Misc # --------------------------------------------------------------- -# `uint64(2**16)` (= 65,536) +# `uint64(2**16)` (= 65,536) MAX_STEMS: 65536 # `uint64(33)` MAX_COMMITMENTS_PER_STEM: 33 diff --git a/presets/mainnet/electra.yaml b/presets/mainnet/electra.yaml index a1a3501277..42afbb233e 100644 --- a/presets/mainnet/electra.yaml +++ b/presets/mainnet/electra.yaml @@ -29,8 +29,8 @@ WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 MAX_ATTESTER_SLASHINGS_ELECTRA: 1 # `uint64(2**3)` (= 8) MAX_ATTESTATIONS_ELECTRA: 8 -# `uint64(2**0)` (= 1) -MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1 +# `uint64(2**1)` (= 2) +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 # Execution # --------------------------------------------------------------- diff --git a/presets/mainnet/eip7594.yaml b/presets/mainnet/fulu.yaml similarity index 92% rename from presets/mainnet/eip7594.yaml rename to presets/mainnet/fulu.yaml index 813febf26d..84111aba28 100644 --- a/presets/mainnet/eip7594.yaml +++ b/presets/mainnet/fulu.yaml @@ -1,4 +1,4 @@ -# Mainnet preset - EIP7594 +# Mainnet preset - FULU # Misc # --------------------------------------------------------------- diff --git a/presets/minimal/deneb.yaml b/presets/minimal/deneb.yaml index 9d0db086b8..c101de3162 100644 --- a/presets/minimal/deneb.yaml +++ b/presets/minimal/deneb.yaml @@ -5,6 +5,6 @@ # `uint64(4096)` FIELD_ELEMENTS_PER_BLOB: 4096 # [customized] -MAX_BLOB_COMMITMENTS_PER_BLOCK: 16 -# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 4 + 1 + 4 = 9 -KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 9 +MAX_BLOB_COMMITMENTS_PER_BLOCK: 32 +# [customized] `floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK)` = 4 + 1 + 5 = 10 +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 10 diff --git a/presets/minimal/eip6800.yaml b/presets/minimal/eip6800.yaml index 499721e4a3..25e6e19c66 100644 --- a/presets/minimal/eip6800.yaml +++ b/presets/minimal/eip6800.yaml @@ -2,7 +2,7 @@ # Execution # --------------------------------------------------------------- -# `uint64(2**16)` (= 65,536) +# `uint64(2**16)` (= 65,536) MAX_STEMS: 65536 # `uint64(33)` MAX_COMMITMENTS_PER_STEM: 33 diff --git a/presets/minimal/electra.yaml b/presets/minimal/electra.yaml index 1656491655..44e4769756 100644 --- a/presets/minimal/electra.yaml +++ b/presets/minimal/electra.yaml @@ -29,8 +29,8 @@ WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 MAX_ATTESTER_SLASHINGS_ELECTRA: 1 # `uint64(2**3)` (= 8) MAX_ATTESTATIONS_ELECTRA: 8 -# `uint64(2**0)` (= 1) -MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1 +# `uint64(2**1)` (= 2) +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 # Execution # --------------------------------------------------------------- diff --git a/presets/minimal/eip7594.yaml b/presets/minimal/fulu.yaml similarity index 92% rename from presets/minimal/eip7594.yaml rename to presets/minimal/fulu.yaml index 847719a421..1204822fb8 100644 --- a/presets/minimal/eip7594.yaml +++ b/presets/minimal/fulu.yaml @@ -1,4 +1,4 @@ -# Minimal preset - EIP7594 +# Minimal preset - FULU # Misc # --------------------------------------------------------------- diff --git a/pysetup/constants.py b/pysetup/constants.py index 6bf22865b2..0959e795c4 100644 --- a/pysetup/constants.py +++ b/pysetup/constants.py @@ -5,7 +5,7 @@ CAPELLA = 'capella' DENEB = 'deneb' ELECTRA = 'electra' -EIP7594 = 'eip7594' +FULU = 'fulu' EIP6800 = 'eip6800' WHISK = 'whisk' EIP7732 = 'eip7732' diff --git a/pysetup/md_doc_paths.py b/pysetup/md_doc_paths.py index 0f0d1c8593..ff081ad5ab 100644 --- a/pysetup/md_doc_paths.py +++ b/pysetup/md_doc_paths.py @@ -7,8 +7,8 @@ CAPELLA, DENEB, ELECTRA, + FULU, WHISK, - EIP7594, EIP6800, EIP7732, ) @@ -21,8 +21,8 @@ CAPELLA: BELLATRIX, DENEB: CAPELLA, ELECTRA: DENEB, + FULU: ELECTRA, WHISK: CAPELLA, - EIP7594: ELECTRA, EIP6800: DENEB, EIP7732: ELECTRA, } diff --git a/pysetup/spec_builders/__init__.py b/pysetup/spec_builders/__init__.py index c5bbcf39eb..f6e05fc5b9 100644 --- a/pysetup/spec_builders/__init__.py +++ b/pysetup/spec_builders/__init__.py @@ -4,8 +4,8 @@ from .capella import CapellaSpecBuilder from .deneb import DenebSpecBuilder from .electra import ElectraSpecBuilder +from .fulu import FuluSpecBuilder from .whisk import WhiskSpecBuilder -from .eip7594 import EIP7594SpecBuilder from .eip6800 import EIP6800SpecBuilder from .eip7732 import EIP7732SpecBuilder @@ -14,6 +14,6 @@ builder.fork: builder for builder in ( Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder, - ElectraSpecBuilder, WhiskSpecBuilder, EIP7594SpecBuilder, EIP6800SpecBuilder, EIP7732SpecBuilder, + ElectraSpecBuilder, FuluSpecBuilder, WhiskSpecBuilder, EIP6800SpecBuilder, EIP7732SpecBuilder, ) } diff --git a/pysetup/spec_builders/electra.py b/pysetup/spec_builders/electra.py index 6746d9aada..f473dbadc3 100644 --- a/pysetup/spec_builders/electra.py +++ b/pysetup/spec_builders/electra.py @@ -46,7 +46,8 @@ def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadRespo def is_valid_block_hash(self: ExecutionEngine, execution_payload: ExecutionPayload, - parent_beacon_block_root: Root) -> bool: + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes]) -> bool: return True def is_valid_versioned_hashes(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool: diff --git a/pysetup/spec_builders/eip7594.py b/pysetup/spec_builders/fulu.py similarity index 95% rename from pysetup/spec_builders/eip7594.py rename to pysetup/spec_builders/fulu.py index e3177da8ca..3cd0c1c467 100644 --- a/pysetup/spec_builders/eip7594.py +++ b/pysetup/spec_builders/fulu.py @@ -1,11 +1,11 @@ from typing import Dict from .base import BaseSpecBuilder -from ..constants import EIP7594 +from ..constants import FULU -class EIP7594SpecBuilder(BaseSpecBuilder): - fork: str = EIP7594 +class FuluSpecBuilder(BaseSpecBuilder): + fork: str = FULU @classmethod def imports(cls, preset_name: str): diff --git a/scripts/build_run_docker_tests.sh b/scripts/build_run_docker_tests.sh index 91aa2c8ae1..24d70b145d 100755 --- a/scripts/build_run_docker_tests.sh +++ b/scripts/build_run_docker_tests.sh @@ -10,7 +10,7 @@ # Set variables -ALL_EXECUTABLE_SPECS=("phase0" "altair" "bellatrix" "capella" "deneb" "electra" "whisk" "eip7594") +ALL_EXECUTABLE_SPECS=("phase0" "altair" "bellatrix" "capella" "deneb" "electra" "fulu" "whisk") TEST_PRESET_TYPE=minimal FORK_TO_TEST=phase0 WORKDIR="//consensus-specs//tests//core//pyspec" @@ -82,16 +82,16 @@ else echo "Image $IMAGE_NAME already exists. Skipping build..." fi -# Equivalent to `make citest with the subsequent flags` +# Equivalent to `make test with the subsequent flags` if [ "$FORK_TO_TEST" == "all" ]; then for fork in "${ALL_EXECUTABLE_SPECS[@]}"; do docker run --name $CONTAINER_NAME $IMAGE_NAME \ - make citest fork=$fork TEST_PRESET_TYPE=$TEST_PRESET_TYPE + make test fork=$fork preset=$TEST_PRESET_TYPE copy_test_results $fork done else docker run --name $CONTAINER_NAME $IMAGE_NAME \ - make citest fork=$FORK_TO_TEST TEST_PRESET_TYPE=$TEST_PRESET_TYPE + make test fork=$FORK_TO_TEST preset=$TEST_PRESET_TYPE copy_test_results $FORK_TO_TEST fi diff --git a/setup.py b/setup.py index 83ae7accb4..0bc90ae787 100644 --- a/setup.py +++ b/setup.py @@ -561,7 +561,7 @@ def run(self): python_requires=">=3.9, <4", extras_require={ "test": ["pytest>=4.4", "pytest-cov", "pytest-xdist"], - "lint": ["flake8==5.0.4", "mypy==0.981", "pylint==3.3.1"], + "lint": ["flake8==5.0.4", "mypy==0.981", "pylint==3.3.1", "codespell<3.0.0,>=2.0.0"], "generator": ["setuptools>=72.0.0", "pytest>4.4", "python-snappy==0.7.3", "filelock", "pathos==0.3.0"], "docs": ["mkdocs==1.4.2", "mkdocs-material==9.1.5", "mdx-truly-sane-lists==1.3", "mkdocs-awesome-pages-plugin==2.8.0"] }, diff --git a/specs/_features/custody_game/beacon-chain.md b/specs/_features/custody_game/beacon-chain.md index e4effa2e17..092846a484 100644 --- a/specs/_features/custody_game/beacon-chain.md +++ b/specs/_features/custody_game/beacon-chain.md @@ -573,7 +573,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed # Any signed custody-slashing should result in at least one slashing. # If the custody bits are valid, then the claim itself is slashed. - malefactor = state.validators[custody_slashing.malefactor_index] + malefactor = state.validators[custody_slashing.malefactor_index] whistleblower = state.validators[custody_slashing.whistleblower_index] domain = get_domain(state, DOMAIN_CUSTODY_BIT_SLASHING, get_current_epoch(state)) signing_root = compute_signing_root(custody_slashing, domain) @@ -596,7 +596,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed # Verify existence and participation of claimed malefactor attesters = get_attesting_indices(state, attestation) assert custody_slashing.malefactor_index in attesters - + # Verify the malefactor custody key epoch_to_sign = get_randao_epoch_for_custody_period( get_custody_period_for_validator(custody_slashing.malefactor_index, attestation.data.target.epoch), @@ -619,7 +619,7 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed for attester_index in attesters: if attester_index != custody_slashing.malefactor_index: increase_balance(state, attester_index, whistleblower_reward) - # No special whisteblower reward: it is expected to be an attester. Others are free to slash too however. + # No special whisteblower reward: it is expected to be an attester. Others are free to slash too however. else: # The claim was false, the custody bit was correct. Slash the whistleblower that induced this work. slash_validator(state, custody_slashing.whistleblower_index) diff --git a/specs/_features/das/p2p-interface.md b/specs/_features/das/p2p-interface.md index b166c9c3e4..2c165078d2 100644 --- a/specs/_features/das/p2p-interface.md +++ b/specs/_features/das/p2p-interface.md @@ -28,12 +28,10 @@ - - ## Introduction For an introduction about DAS itself, see [the DAS participation spec](sampling.md#data-availability-sampling). -This is not a pre-requisite for the network layer, but will give you valuable context. +This is not a pre-requisite for the network layer, but will give you valuable context. For sampling, all nodes need to query for `k` random samples each slot. @@ -55,13 +53,13 @@ The push model does not aim to serve "historical" queries (anything older than t Historical queries are still required for the unhappy case, where messages are not pushed quick enough, and missing samples are not reconstructed by other nodes on the horizontal subnet quick enough. -The main challenge in supporting historical queries is to target the right nodes, +The main challenge in supporting historical queries is to target the right nodes, without concentrating too many requests on a single node, or breaking the network/consensus identity separation. ## DAS Subnets On a high level, the push-model roles are divided into: -- Sources: create blobs of shard block data, and transformed into many tiny samples. +- Sources: create blobs of shard block data, and transformed into many tiny samples. - Sinks: continuously look for samples At full operation, the network has one proposer, per shard, per slot. @@ -93,15 +91,15 @@ Peers on the horizontal subnet are expected to at least perform regular propagat Nodes on this same subnet can replicate the sampling efficiently (including a proof for each sample), and distribute it to any vertical networks that are available to them. -Since the messages are content-addressed (instead of origin-stamped), -multiple publishers of the same samples on a vertical subnet do not hurt performance, +Since the messages are content-addressed (instead of origin-stamped), +multiple publishers of the same samples on a vertical subnet do not hurt performance, but actually improve it by shortcutting regular propagation on the vertical subnet, and thus lowering the latency to a sample. ### Vertical subnets Vertical subnets propagate the samples to every peer that is interested. -These interests are randomly sampled and rotate quickly: although not perfect, +These interests are randomly sampled and rotate quickly: although not perfect, sufficient to avoid any significant amount of nodes from being 100% predictable. As soon as a sample is missing after the expected propagation time window, @@ -166,7 +164,7 @@ The [DAS participation spec](sampling.md#horizontal-subnets) outlines when and w #### Vertical subnets: `das_sample_{subnet_index}` -Shard blob samples can be verified with just a 48 byte KZG proof (commitment quotient polynomial), +Shard blob samples can be verified with just a 48 byte KZG proof (commitment quotient polynomial), against the commitment to blob polynomial, specific to that `(shard, slot)` key. The following validations MUST pass before forwarding the `sample` on the vertical subnet. @@ -192,7 +190,7 @@ This is to serve other peers that may have missed it. To pull samples from nodes, in case of network instability when samples are unavailable, a new query method is added to the Req-Resp domain. -This builds on top of the protocol identification and encoding spec which was introduced in [the Phase0 network spec](../../phase0/p2p-interface.md). +This builds on top of the protocol identification and encoding spec which was introduced in [the Phase0 network spec](../../phase0/p2p-interface.md). Note that DAS networking uses a different protocol prefix: `/eth2/das/req` diff --git a/specs/_features/eip7594/beacon-chain.md b/specs/_features/eip7594/beacon-chain.md deleted file mode 100644 index 28031178e1..0000000000 --- a/specs/_features/eip7594/beacon-chain.md +++ /dev/null @@ -1,140 +0,0 @@ -# EIP7594 -- The Beacon Chain - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [Configuration](#configuration) - - [Execution](#execution) - - [Execution payload](#execution-payload) - - [Modified `process_execution_payload`](#modified-process_execution_payload) -- [Testing](#testing) - - - - -## Introduction - -*Note:* This specification is built upon [Electra](../electra/beacon-chain.md) and is under active development. - -## Configuration - -### Execution - -| Name | Value | Description | -| - | - | - | -| `MAX_BLOBS_PER_BLOCK_EIP7594` | `uint64(8)` | *[New in EIP7594]* Maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` | - -#### Execution payload - -##### Modified `process_execution_payload` - -```python -def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: - payload = body.execution_payload - - # Verify consistency of the parent hash with respect to the previous execution payload header - assert payload.parent_hash == state.latest_execution_payload_header.block_hash - # Verify prev_randao - assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) - # Verify timestamp - assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) - # Verify commitments are under limit - assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_EIP7594 # [Modified in EIP7594] - # Verify the execution payload is valid - versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] - assert execution_engine.verify_and_notify_new_payload( - NewPayloadRequest( - execution_payload=payload, - versioned_hashes=versioned_hashes, - parent_beacon_block_root=state.latest_block_header.parent_root, - execution_requests=body.execution_requests, - ) - ) - # Cache execution payload header - state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=payload.parent_hash, - fee_recipient=payload.fee_recipient, - state_root=payload.state_root, - receipts_root=payload.receipts_root, - logs_bloom=payload.logs_bloom, - prev_randao=payload.prev_randao, - block_number=payload.block_number, - gas_limit=payload.gas_limit, - gas_used=payload.gas_used, - timestamp=payload.timestamp, - extra_data=payload.extra_data, - base_fee_per_gas=payload.base_fee_per_gas, - block_hash=payload.block_hash, - transactions_root=hash_tree_root(payload.transactions), - withdrawals_root=hash_tree_root(payload.withdrawals), - blob_gas_used=payload.blob_gas_used, - excess_blob_gas=payload.excess_blob_gas, - ) -``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure EIP7594 testing only. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=EIP7594_FORK_VERSION, # [Modified in EIP7594] for testing only - current_version=EIP7594_FORK_VERSION, # [Modified in EIP7594] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - deposit_requests_start_index=UNSET_DEPOSIT_REQUESTS_START_INDEX, - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process deposit balance updates - validator_pubkeys = [v.pubkey for v in state.validators] - for deposit in state.pending_deposits: - validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) - increase_balance(state, validator_index, deposit.amount) - state.pending_deposits = [] - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min( - balance - balance % EFFECTIVE_BALANCE_INCREMENT, get_max_effective_balance(validator)) - if validator.effective_balance >= MIN_ACTIVATION_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # Initialize the execution payload header - state.latest_execution_payload_header = execution_payload_header - - return state -``` diff --git a/specs/_features/eip7732/beacon-chain.md b/specs/_features/eip7732/beacon-chain.md index 30651cedbe..2303e33d40 100644 --- a/specs/_features/eip7732/beacon-chain.md +++ b/specs/_features/eip7732/beacon-chain.md @@ -61,34 +61,34 @@ ## Introduction -This is the beacon chain specification of the enshrined proposer builder separation feature. +This is the beacon chain specification of the enshrined proposer builder separation feature. *Note:* This specification is built upon [Electra](../../electra/beacon-chain.md) and is under active development. This feature adds new staked consensus participants called *Builders* and new honest validators duties called *payload timeliness attestations*. The slot is divided in **four** intervals. Honest validators gather *signed bids* (a `SignedExecutionPayloadHeader`) from builders and submit their consensus blocks (a `SignedBeaconBlock`) including these bids at the beginning of the slot. At the start of the second interval, honest validators submit attestations just as they do previous to this feature). At the start of the third interval, aggregators aggregate these attestations and the builder broadcasts either a full payload or a message indicating that they are withholding the payload (a `SignedExecutionPayloadEnvelope`). At the start of the fourth interval, some validators selected to be members of the new **Payload Timeliness Committee** (PTC) attest to the presence and timeliness of the builder's payload. -At any given slot, the status of the blockchain's head may be either -- A block from a previous slot (e.g. the current slot's proposer did not submit its block). -- An *empty* block from the current slot (e.g. the proposer submitted a timely block, but the builder did not reveal the payload on time). -- A full block for the current slot (both the proposer and the builder revealed on time). +At any given slot, the status of the blockchain's head may be either +- A block from a previous slot (e.g. the current slot's proposer did not submit its block). +- An *empty* block from the current slot (e.g. the proposer submitted a timely block, but the builder did not reveal the payload on time). +- A full block for the current slot (both the proposer and the builder revealed on time). ## Constants ### Payload status -| Name | Value | -| - | - | +| Name | Value | +| - | - | | `PAYLOAD_ABSENT` | `uint8(0)` | -| `PAYLOAD_PRESENT` | `uint8(1)` | -| `PAYLOAD_WITHHELD` | `uint8(2)` | +| `PAYLOAD_PRESENT` | `uint8(1)` | +| `PAYLOAD_WITHHELD` | `uint8(2)` | | `PAYLOAD_INVALID_STATUS` | `uint8(3)` | ## Preset ### Misc -| Name | Value | -| - | - | +| Name | Value | +| - | - | | `PTC_SIZE` | `uint64(2**9)` (=512) # (New in EIP-7732) | ### Domain types @@ -151,7 +151,7 @@ class SignedExecutionPayloadHeader(Container): message: ExecutionPayloadHeader signature: BLSSignature ``` - + #### `ExecutionPayloadEnvelope` ```python @@ -177,7 +177,7 @@ class SignedExecutionPayloadEnvelope(Container): #### `BeaconBlockBody` -**Note:** The Beacon Block body is modified to contain a `Signed ExecutionPayloadHeader`. The containers `BeaconBlock` and `SignedBeaconBlock` are modified indirectly. The field `execution_requests` is removed from the beacon block body and moved into the signed execution payload envelope. +**Note:** The Beacon Block body is modified to contain a `Signed ExecutionPayloadHeader`. The containers `BeaconBlock` and `SignedBeaconBlock` are modified indirectly. The field `execution_requests` is removed from the beacon block body and moved into the signed execution payload envelope. ```python class BeaconBlockBody(Container): @@ -203,7 +203,7 @@ class BeaconBlockBody(Container): #### `ExecutionPayloadHeader` -**Note:** The `ExecutionPayloadHeader` is modified to only contain the block hash of the committed `ExecutionPayload` in addition to the builder's payment information, gas limit and KZG commitments root to verify the inclusion proofs. +**Note:** The `ExecutionPayloadHeader` is modified to only contain the block hash of the committed `ExecutionPayload` in addition to the builder's payment information, gas limit and KZG commitments root to verify the inclusion proofs. ```python class ExecutionPayloadHeader(Container): @@ -219,7 +219,7 @@ class ExecutionPayloadHeader(Container): #### `BeaconState` -*Note*: The `BeaconState` is modified to track the last withdrawals honored in the CL. The `latest_execution_payload_header` is modified semantically to refer not to a past committed `ExecutionPayload` but instead it corresponds to the state's slot builder's bid. Another addition is to track the last committed block hash and the last slot that was full, that is in which there were both consensus and execution blocks included. +*Note*: The `BeaconState` is modified to track the last withdrawals honored in the CL. The `latest_execution_payload_header` is modified semantically to refer not to a past committed `ExecutionPayload` but instead it corresponds to the state's slot builder's bid. Another addition is to track the last committed block hash and the last slot that was full, that is in which there were both consensus and execution blocks included. ```python class BeaconState(Container): @@ -311,7 +311,7 @@ def remove_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlag ```python def is_valid_indexed_payload_attestation( - state: BeaconState, + state: BeaconState, indexed_payload_attestation: IndexedPayloadAttestation) -> bool: """ Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has @@ -335,7 +335,7 @@ def is_valid_indexed_payload_attestation( #### `is_parent_block_full` -This function returns true if the last committed payload header was fulfilled with a payload, this can only happen when both beacon block and payload were present. This function must be called on a beacon state before processing the execution payload header in the block. +This function returns true if the last committed payload header was fulfilled with a payload, this can only happen when both beacon block and payload were present. This function must be called on a beacon state before processing the execution payload header in the block. ```python def is_parent_block_full(state: BeaconState) -> bool: @@ -354,8 +354,8 @@ def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]: epoch = compute_epoch_at_slot(slot) committees_per_slot = bit_floor(min(get_committee_count_per_slot(state, epoch), PTC_SIZE)) members_per_committee = PTC_SIZE // committees_per_slot - - validator_indices: List[ValidatorIndex] = [] + + validator_indices: List[ValidatorIndex] = [] for idx in range(committees_per_slot): beacon_committee = get_beacon_committee(state, slot, CommitteeIndex(idx)) validator_indices += beacon_committee[:members_per_committee] @@ -390,7 +390,7 @@ def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[V #### `get_payload_attesting_indices` ```python -def get_payload_attesting_indices(state: BeaconState, slot: Slot, +def get_payload_attesting_indices(state: BeaconState, slot: Slot, payload_attestation: PayloadAttestation) -> Set[ValidatorIndex]: """ Return the set of attesting indices corresponding to ``payload_attestation``. @@ -402,7 +402,7 @@ def get_payload_attesting_indices(state: BeaconState, slot: Slot, #### `get_indexed_payload_attestation` ```python -def get_indexed_payload_attestation(state: BeaconState, slot: Slot, +def get_indexed_payload_attestation(state: BeaconState, slot: Slot, payload_attestation: PayloadAttestation) -> IndexedPayloadAttestation: """ Return the indexed payload attestation corresponding to ``payload_attestation``. @@ -442,7 +442,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ##### Modified `process_withdrawals` -**Note:** This is modified to take only the `state` as parameter. Withdrawals are deterministic given the beacon state, any execution payload that has the corresponding block as parent beacon block is required to honor these withdrawals in the execution layer. This function must be called before `process_execution_payload_header` as this latter function affects validator balances. +**Note:** This is modified to take only the `state` as parameter. Withdrawals are deterministic given the beacon state, any execution payload that has the corresponding block as parent beacon block is required to honor these withdrawals in the execution layer. This function must be called before `process_execution_payload_header` as this latter function affects validator balances. ```python def process_withdrawals(state: BeaconState) -> None: @@ -481,7 +481,7 @@ def process_withdrawals(state: BeaconState) -> None: ##### New `verify_execution_payload_header_signature` ```python -def verify_execution_payload_header_signature(state: BeaconState, +def verify_execution_payload_header_signature(state: BeaconState, signed_header: SignedExecutionPayloadHeader) -> bool: # Check the signature builder = state.validators[signed_header.message.builder_index] @@ -516,7 +516,7 @@ def process_execution_payload_header(state: BeaconState, block: BeaconBlock) -> decrease_balance(state, builder_index, amount) increase_balance(state, block.proposer_index, amount) - # Cache the signed execution payload header + # Cache the signed execution payload header state.latest_execution_payload_header = header ``` @@ -557,7 +557,7 @@ def process_payload_attestation(state: BeaconState, payload_attestation: Payload data = payload_attestation.data assert data.beacon_block_root == state.latest_block_header.parent_root # Check that the attestation is for the previous slot - assert data.slot + 1 == state.slot + assert data.slot + 1 == state.slot # Verify signature indexed_payload_attestation = get_indexed_payload_attestation(state, data.slot, payload_attestation) @@ -658,11 +658,11 @@ def verify_execution_payload_envelope_signature( *Note*: `process_execution_payload` is now an independent check in state transition. It is called when importing a signed execution payload proposed by the builder of the current slot. ```python -def process_execution_payload(state: BeaconState, - signed_envelope: SignedExecutionPayloadEnvelope, +def process_execution_payload(state: BeaconState, + signed_envelope: SignedExecutionPayloadEnvelope, execution_engine: ExecutionEngine, verify: bool = True) -> None: # Verify signature - if verify: + if verify: assert verify_execution_payload_envelope_signature(state, signed_envelope) envelope = signed_envelope.message payload = envelope.payload @@ -670,7 +670,7 @@ def process_execution_payload(state: BeaconState, previous_state_root = hash_tree_root(state) if state.latest_block_header.state_root == Root(): state.latest_block_header.state_root = previous_state_root - + # Verify consistency with the beacon block assert envelope.beacon_block_root == hash_tree_root(state.latest_block_header) @@ -679,14 +679,14 @@ def process_execution_payload(state: BeaconState, assert envelope.builder_index == committed_header.builder_index assert committed_header.blob_kzg_commitments_root == hash_tree_root(envelope.blob_kzg_commitments) - if not envelope.payload_withheld: + if not envelope.payload_withheld: # Verify the withdrawals root assert hash_tree_root(payload.withdrawals) == state.latest_withdrawals_root # Verify the gas_limit assert committed_header.gas_limit == payload.gas_limit - assert committed_header.block_hash == payload.block_hash + assert committed_header.block_hash == payload.block_hash # Verify consistency of the parent hash with respect to the previous execution payload assert payload.parent_hash == state.latest_block_hash # Verify prev_randao @@ -696,7 +696,7 @@ def process_execution_payload(state: BeaconState, # Verify commitments are under limit assert len(envelope.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK # Verify the execution payload is valid - versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) + versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in envelope.blob_kzg_commitments] requests = envelope.execution_requests assert execution_engine.verify_and_notify_new_payload( @@ -722,6 +722,6 @@ def process_execution_payload(state: BeaconState, state.latest_full_slot = state.slot # Verify the state root - if verify: + if verify: assert envelope.state_root == hash_tree_root(state) ``` diff --git a/specs/_features/eip7732/builder.md b/specs/_features/eip7732/builder.md index 491f625d89..7c793b07e6 100644 --- a/specs/_features/eip7732/builder.md +++ b/specs/_features/eip7732/builder.md @@ -16,20 +16,20 @@ This is an accompanying document which describes the expected actions of a "buil ## Introduction -With the EIP-7732 Fork, the protocol includes new staked participants of the protocol called *Builders*. While Builders are a subset of the validator set, they have extra attributions that are optional. Validators may opt to not be builders and as such we collect the set of guidelines for those validators that want to act as builders in this document. +With the EIP-7732 Fork, the protocol includes new staked participants of the protocol called *Builders*. While Builders are a subset of the validator set, they have extra attributions that are optional. Validators may opt to not be builders and as such we collect the set of guidelines for those validators that want to act as builders in this document. ## Builders attributions -Builders can submit bids to produce execution payloads. They can broadcast these bids in the form of `SignedExecutionPayloadHeader` objects, these objects encode a commitment to reveal an execution payload in exchange for a payment. When their bids are chosen by the corresponding proposer, builders are expected to broadcast an accompanying `SignedExecutionPayloadEnvelope` object honoring the commitment. +Builders can submit bids to produce execution payloads. They can broadcast these bids in the form of `SignedExecutionPayloadHeader` objects, these objects encode a commitment to reveal an execution payload in exchange for a payment. When their bids are chosen by the corresponding proposer, builders are expected to broadcast an accompanying `SignedExecutionPayloadEnvelope` object honoring the commitment. -Thus, builders tasks are divided in two, submitting bids, and submitting payloads. +Thus, builders tasks are divided in two, submitting bids, and submitting payloads. ### Constructing the payload bid -Builders can broadcast a payload bid for the current or the next slot's proposer to include. They produce a `SignedExecutionPayloadHeader` as follows. +Builders can broadcast a payload bid for the current or the next slot's proposer to include. They produce a `SignedExecutionPayloadHeader` as follows. 1. Set `header.parent_block_hash` to the current head of the execution chain (this can be obtained from the beacon state as `state.last_block_hash`). -2. Set `header.parent_block_root` to be the head of the consensus chain (this can be obtained from the beacon state as `hash_tree_root(state.latest_block_header)`. The `parent_block_root` and `parent_block_hash` must be compatible, in the sense that they both should come from the same `state` by the method described in this and the previous point. +2. Set `header.parent_block_root` to be the head of the consensus chain (this can be obtained from the beacon state as `hash_tree_root(state.latest_block_header)`. The `parent_block_root` and `parent_block_hash` must be compatible, in the sense that they both should come from the same `state` by the method described in this and the previous point. 3. Construct an execution payload. This can be performed with an external execution engine with a call to `engine_getPayloadV4`. 4. Set `header.block_hash` to be the block hash of the constructed payload, that is `payload.block_hash`. 5. Set `header.gas_limit` to be the gas limit of the constructed payload, that is `payload.gas_limit`. @@ -48,13 +48,13 @@ def get_execution_payload_header_signature( return bls.Sign(privkey, signing_root) ``` -The builder assembles then `signed_execution_payload_header = SignedExecutionPayloadHeader(message=header, signature=signature)` and broadcasts it on the `execution_payload_header` global gossip topic. +The builder assembles then `signed_execution_payload_header = SignedExecutionPayloadHeader(message=header, signature=signature)` and broadcasts it on the `execution_payload_header` global gossip topic. ### Constructing the `BlobSidecar`s [Modified in EIP-7732] -The `BlobSidecar` container is modified indirectly because the constant `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified. The function `get_blob_sidecars` is modified because the KZG commitments are no longer included in the beacon block but rather in the `ExecutionPayloadEnvelope`, the builder has to send the commitments as parameters to this function. +The `BlobSidecar` container is modified indirectly because the constant `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified. The function `get_blob_sidecars` is modified because the KZG commitments are no longer included in the beacon block but rather in the `ExecutionPayloadEnvelope`, the builder has to send the commitments as parameters to this function. ```python def get_blob_sidecars(signed_block: SignedBeaconBlock, @@ -100,19 +100,19 @@ def get_blob_sidecars(signed_block: SignedBeaconBlock, ### Constructing the execution payload envelope -When the proposer publishes a valid `SignedBeaconBlock` containing a signed commitment by the builder, the builder is later expected to broadcast the corresponding `SignedExecutionPayloadEnvelope` that fulfills this commitment. See below for a special case of an *honestly withheld payload*. +When the proposer publishes a valid `SignedBeaconBlock` containing a signed commitment by the builder, the builder is later expected to broadcast the corresponding `SignedExecutionPayloadEnvelope` that fulfills this commitment. See below for a special case of an *honestly withheld payload*. -To construct the `execution_payload_envelope` the builder must perform the following steps, we alias `header` to be the committed `ExecutionPayloadHeader` in the beacon block. +To construct the `execution_payload_envelope` the builder must perform the following steps, we alias `header` to be the committed `ExecutionPayloadHeader` in the beacon block. -1. Set the `payload` field to be the `ExecutionPayload` constructed when creating the corresponding bid. This payload **MUST** have the same block hash as `header.block_hash`. -2. Set the `builder_index` field to be the validator index of the builder performing these steps. This field **MUST** be `header.builder_index`. +1. Set the `payload` field to be the `ExecutionPayload` constructed when creating the corresponding bid. This payload **MUST** have the same block hash as `header.block_hash`. +2. Set the `builder_index` field to be the validator index of the builder performing these steps. This field **MUST** be `header.builder_index`. 3. Set `beacon_block_root` to be the `hash_tree_root` of the corresponding beacon block. 4. Set `blob_kzg_commitments` to be the `commitments` field of the blobs bundle constructed when constructing the bid. This field **MUST** have a `hash_tree_root` equal to `header.blob_kzg_commitments_root`. 5. Set `payload_witheld` to `False`. After setting these parameters, the builder should run `process_execution_payload(state, signed_envelope, verify=False)` and this function should not trigger an exception. -6. Set `state_root` to `hash_tree_root(state)`. +6. Set `state_root` to `hash_tree_root(state)`. After preparing the `envelope` the builder should sign the envelope using: ```python def get_execution_payload_envelope_signature( @@ -121,8 +121,8 @@ def get_execution_payload_envelope_signature( signing_root = compute_signing_root(envelope, domain) return bls.Sign(privkey, signing_root) ``` -The builder assembles then `signed_execution_payload_envelope = SignedExecutionPayloadEnvelope(message=envelope, signature=signature)` and broadcasts it on the `execution_payload` global gossip topic. +The builder assembles then `signed_execution_payload_envelope = SignedExecutionPayloadEnvelope(message=envelope, signature=signature)` and broadcasts it on the `execution_payload` global gossip topic. ### Honest payload withheld messages -An honest builder that has seen a `SignedBeaconBlock` referencing his signed bid, but that block was not timely and thus it is not the head of the builder's chain, may choose to withhold their execution payload. For this the builder should simply act as if it were building an empty payload, without any transactions, withdrawals, etc. The `payload.block_hash` may not be equal to `header.block_hash`. The builder may then sets `payload_withheld` to `True`. If the PTC sees this message and votes for it, validators will attribute a *withholding boost* to the builder, which would increase the forkchoice weight of the parent block, favoring it and preventing the builder from being charged for the bid by not revealing. +An honest builder that has seen a `SignedBeaconBlock` referencing his signed bid, but that block was not timely and thus it is not the head of the builder's chain, may choose to withhold their execution payload. For this the builder should simply act as if it were building an empty payload, without any transactions, withdrawals, etc. The `payload.block_hash` may not be equal to `header.block_hash`. The builder may then sets `payload_withheld` to `True`. If the PTC sees this message and votes for it, validators will attribute a *withholding boost* to the builder, which would increase the forkchoice weight of the parent block, favoring it and preventing the builder from being charged for the bid by not revealing. diff --git a/specs/_features/eip7732/fork-choice.md b/specs/_features/eip7732/fork-choice.md index 0eb49ddfc1..a52e959cba 100644 --- a/specs/_features/eip7732/fork-choice.md +++ b/specs/_features/eip7732/fork-choice.md @@ -44,15 +44,15 @@ This is the modification of the fork choice accompanying the EIP-7732 upgrade. | Name | Value | | -------------------- | ----------- | -| `PAYLOAD_TIMELY_THRESHOLD` | `PTC_SIZE / 2` (=`uint64(256)`) | +| `PAYLOAD_TIMELY_THRESHOLD` | `PTC_SIZE / 2` (=`uint64(256)`) | | `INTERVALS_PER_SLOT` | `4` # [modified in EIP-7732] | -| `PROPOSER_SCORE_BOOST` | `20` # [modified in EIP-7732] | -| `PAYLOAD_WITHHOLD_BOOST` | `40` | -| `PAYLOAD_REVEAL_BOOST` | `40` | +| `PROPOSER_SCORE_BOOST` | `20` # [modified in EIP-7732] | +| `PAYLOAD_WITHHOLD_BOOST` | `40` | +| `PAYLOAD_REVEAL_BOOST` | `40` | ## Containers -### New `ChildNode` +### New `ChildNode` Auxiliary class to consider `(block, slot, bool)` LMD voting ```python @@ -75,7 +75,7 @@ class LatestMessage(object): ``` ### Modified `update_latest_messages` -**Note:** the function `update_latest_messages` is updated to use the attestation slot instead of target. Notice that this function is only called on validated attestations and validators cannot attest twice in the same epoch without equivocating. Notice also that target epoch number and slot number are validated on `validate_on_attestation`. +**Note:** the function `update_latest_messages` is updated to use the attestation slot instead of target. Notice that this function is only called on validated attestations and validators cannot attest twice in the same epoch without equivocating. Notice also that target epoch number and slot number are validated on `validate_on_attestation`. ```python def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: @@ -87,8 +87,8 @@ def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIn store.latest_messages[i] = LatestMessage(slot=slot, root=beacon_block_root) ``` -### Modified `Store` -**Note:** `Store` is modified to track the intermediate states of "empty" consensus blocks, that is, those consensus blocks for which the corresponding execution payload has not been revealed or has not been included on chain. +### Modified `Store` +**Note:** `Store` is modified to track the intermediate states of "empty" consensus blocks, that is, those consensus blocks for which the corresponding execution payload has not been revealed or has not been included on chain. ```python @dataclass @@ -114,7 +114,7 @@ class Store(object): ptc_vote: Dict[Root, Vector[uint8, PTC_SIZE]] = field(default_factory=dict) # [New in EIP-7732] ``` -### Modified `get_forkchoice_store` +### Modified `get_forkchoice_store` ```python def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: @@ -162,8 +162,8 @@ def notify_ptc_messages(store: Store, state: BeaconState, payload_attestations: store, PayloadAttestationMessage( validator_index=idx, - data=payload_attestation.data, - signature=BLSSignature(), + data=payload_attestation.data, + signature=BLSSignature(), is_from_block=True ) ) @@ -174,7 +174,7 @@ def notify_ptc_messages(store: Store, state: BeaconState, payload_attestations: ```python def is_payload_present(store: Store, beacon_block_root: Root) -> bool: """ - Return whether the execution payload for the beacon block with root ``beacon_block_root`` was voted as present + Return whether the execution payload for the beacon block with root ``beacon_block_root`` was voted as present by the PTC """ # The beacon block root must be known @@ -192,15 +192,15 @@ def is_parent_node_full(store: Store, block: BeaconBlock) -> bool: return parent_block_hash == message_block_hash ``` -### Modified `get_ancestor` -**Note:** `get_ancestor` is modified to return whether the chain is based on an *empty* or *full* block. +### Modified `get_ancestor` +**Note:** `get_ancestor` is modified to return whether the chain is based on an *empty* or *full* block. ```python def get_ancestor(store: Store, root: Root, slot: Slot) -> ChildNode: """ - Returns the beacon block root, the slot and the payload status of the ancestor of the beacon block - with ``root`` at ``slot``. If the beacon block with ``root`` is already at ``slot`` or we are - requesting an ancestor "in the future" it returns its PTC status instead of the actual payload content. + Returns the beacon block root, the slot and the payload status of the ancestor of the beacon block + with ``root`` at ``slot``. If the beacon block with ``root`` is already at ``slot`` or we are + requesting an ancestor "in the future" it returns its PTC status instead of the actual payload content. """ block = store.blocks[root] if block.slot <= slot: @@ -235,7 +235,7 @@ def is_supporting_vote(store: Store, node: ChildNode, message: LatestMessage) -> """ if node.root == message.root: # an attestation for a given root always counts for that root regardless if full or empty - # as long as the attestation happened after the requested slot. + # as long as the attestation happened after the requested slot. return node.slot <= message.slot message_block = store.blocks[message.root] if node.slot >= message_block.slot: @@ -245,7 +245,7 @@ def is_supporting_vote(store: Store, node: ChildNode, message: LatestMessage) -> ``` ### New `compute_proposer_boost` -This is a helper to compute the proposer boost. It applies the proposer boost to any ancestor of the proposer boost root taking into account the payload presence. There is one exception: if the requested node has the same root and slot as the block with the proposer boost root, then the proposer boost is applied to both empty and full versions of the node. +This is a helper to compute the proposer boost. It applies the proposer boost to any ancestor of the proposer boost root taking into account the payload presence. There is one exception: if the requested node has the same root and slot as the block with the proposer boost root, then the proposer boost is applied to both empty and full versions of the node. ```python def compute_proposer_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei: if store.proposer_boost_root == Root(): @@ -283,7 +283,7 @@ def compute_withhold_boost(store: Store, state: BeaconState, node: ChildNode) -> ``` ### New `compute_reveal_boost` -This is a similar helper to the last two, the only difference is that the reveal boost is only applied to the full version of the node when querying for the same slot as the revealed payload. +This is a similar helper to the last two, the only difference is that the reveal boost is only applied to the full version of the node when querying for the same slot as the revealed payload. ```python def compute_reveal_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei: @@ -302,7 +302,7 @@ def compute_reveal_boost(store: Store, state: BeaconState, node: ChildNode) -> G ### Modified `get_weight` -**Note:** `get_weight` is modified to only count votes for descending chains that support the status of a triple `Root, Slot, bool`, where the `bool` indicates if the block was full or not. `Slot` is needed for a correct implementation of `(Block, Slot)` voting. +**Note:** `get_weight` is modified to only count votes for descending chains that support the status of a triple `Root, Slot, bool`, where the `bool` indicates if the block was full or not. `Slot` is needed for a correct implementation of `(Block, Slot)` voting. ```python def get_weight(store: Store, node: ChildNode) -> Gwei: @@ -326,7 +326,7 @@ def get_weight(store: Store, node: ChildNode) -> Gwei: return attestation_score + proposer_score + builder_reveal_score + builder_withhold_score ``` -### Modified `get_head` +### Modified `get_head` **Note:** `get_head` is a modified to use the new `get_weight` function. It returns the `ChildNode` object corresponidng to the head block. @@ -343,13 +343,13 @@ def get_head(store: Store) -> ChildNode: while True: children = [ ChildNode(root=root, slot=block.slot, is_payload_present=present) for (root, block) in blocks.items() - if block.parent_root == best_child.root and block.slot > best_child.slot and + if block.parent_root == best_child.root and block.slot > best_child.slot and (best_child.root == justified_root or is_parent_node_full(store, block) == best_child.is_payload_present) for present in (True, False) if root in store.execution_payload_states or not present ] if len(children) == 0: return best_child - # if we have children we consider the current head advanced as a possible head + # if we have children we consider the current head advanced as a possible head highest_child_slot = max(child.slot for child in children) children += [ ChildNode(root=best_child.root, slot=best_child.slot + 1, is_payload_present=best_child.is_payload_present) @@ -360,10 +360,10 @@ def get_head(store: Store) -> ChildNode: # Ties are then broken by favoring full blocks # Ties then broken by favoring block with lexicographically higher root new_best_child = max(children, key=lambda child: ( - get_weight(store, child), + get_weight(store, child), blocks[child.root].slot, - is_payload_present(store, child.root), - child.is_payload_present, + is_payload_present(store, child.root), + child.is_payload_present, child.root ) ) @@ -376,7 +376,7 @@ def get_head(store: Store) -> ChildNode: ### Modified `on_block` -*Note*: The handler `on_block` is modified to consider the pre `state` of the given consensus beacon block depending not only on the parent block root, but also on the parent blockhash. In addition we delay the checking of blob data availability until the processing of the execution payload. +*Note*: The handler `on_block` is modified to consider the pre `state` of the given consensus beacon block depending not only on the parent block root, but also on the parent blockhash. In addition we delay the checking of blob data availability until the processing of the execution payload. ```python def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: @@ -449,14 +449,14 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: ### New `on_execution_payload` -The handler `on_execution_payload` is called when the node receives a `SignedExecutionPayloadEnvelope` to sync. +The handler `on_execution_payload` is called when the node receives a `SignedExecutionPayloadEnvelope` to sync. ```python def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None: """ Run ``on_execution_payload`` upon receiving a new execution payload. """ - envelope = signed_envelope.message + envelope = signed_envelope.message # The corresponding beacon block root needs to be known assert envelope.beacon_block_root in store.block_states @@ -472,7 +472,7 @@ def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEn # Add new state for this payload to the store store.execution_payload_states[envelope.beacon_block_root] = state -``` +``` ### `seconds_into_slot` @@ -497,7 +497,7 @@ def on_tick_per_slot(store: Store, time: uint64) -> None: # If this is a new slot, reset store.proposer_boost_root if current_slot > previous_slot: store.proposer_boost_root = Root() - else: + else: # Reset the payload boost if this is the attestation time if seconds_into_slot(store) >= SECONDS_PER_SLOT // INTERVALS_PER_SLOT: store.payload_withhold_boost_root = Root() @@ -534,7 +534,7 @@ def on_payload_attestation_message( assert data.slot == get_current_slot(store) # Verify the signature assert is_valid_indexed_payload_attestation( - state, + state, IndexedPayloadAttestation( attesting_indices=[ptc_message.validator_index], data=data, @@ -545,7 +545,7 @@ def on_payload_attestation_message( ptc_index = ptc.index(ptc_message.validator_index) ptc_vote = store.ptc_vote[data.beacon_block_root] ptc_vote[ptc_index] = data.payload_status - + # Only update payload boosts with attestations from a block if the block is for the current slot and it's early if is_from_block and data.slot + 1 != get_current_slot(store): return diff --git a/specs/_features/eip7732/fork.md b/specs/_features/eip7732/fork.md index 120e64fa46..fa03eb6886 100644 --- a/specs/_features/eip7732/fork.md +++ b/specs/_features/eip7732/fork.md @@ -61,7 +61,7 @@ def compute_fork_version(epoch: Epoch) -> Version: ### Fork trigger -TBD. This fork is defined for testing purposes, the EIP may be combined with other +TBD. This fork is defined for testing purposes, the EIP may be combined with other consensus-layer upgrade. For now, we assume the condition will be triggered at epoch `EIP7732_FORK_EPOCH`. diff --git a/specs/_features/eip7732/p2p-interface.md b/specs/_features/eip7732/p2p-interface.md index d33dfaba31..df02cc2382 100644 --- a/specs/_features/eip7732/p2p-interface.md +++ b/specs/_features/eip7732/p2p-interface.md @@ -1,10 +1,9 @@ # EIP-7732 -- Networking -This document contains the consensus-layer networking specification for EIP7732. - +- [Introduction](#introduction) - [Modification in EIP-7732](#modification-in-eip-7732) - [Preset](#preset) - [Configuration](#configuration) @@ -28,6 +27,12 @@ This document contains the consensus-layer networking specification for EIP7732. +## Introduction + +This document contains the consensus-layer networking specification for EIP7732. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + ## Modification in EIP-7732 ### Preset @@ -67,7 +72,7 @@ class BlobSidecar(Container): ##### Modified `verify_blob_sidecar_inclusion_proof` -`verify_blob_sidecar_inclusion_proof` is modified in EIP-7732 to account for the fact that the KZG commitments are included in the `ExecutionPayloadEnvelope` and no longer in the beacon block body. +`verify_blob_sidecar_inclusion_proof` is modified in EIP-7732 to account for the fact that the KZG commitments are included in the `ExecutionPayloadEnvelope` and no longer in the beacon block body. ```python def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool: @@ -121,7 +126,7 @@ EIP-7732 introduces new global topics for execution header, execution payload an [Modified in EIP-7732] -The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in [the Beacon Chain changes](./beacon-chain.md). +The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in [the Beacon Chain changes](./beacon-chain.md). There are no new validations for this topic. However, all validations with regards to the `ExecutionPayload` are removed: @@ -137,7 +142,7 @@ There are no new validations for this topic. However, all validations with regar And instead the following validations are set in place with the alias `header = signed_execution_payload_header.message`: - If `execution_payload` verification of block's execution payload parent by an execution node **is complete**: - - [REJECT] The block's execution payload parent (defined by `header.parent_block_hash`) passes all validation. + - [REJECT] The block's execution payload parent (defined by `header.parent_block_hash`) passes all validation. - [REJECT] The block's parent (defined by `block.parent_root`) passes validation. ###### `execution_payload` @@ -148,12 +153,12 @@ The following validations MUST pass before forwarding the `signed_execution_payl - _[IGNORE]_ The envelope's block root `envelope.block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue payload for processing once the block is retrieved). - _[IGNORE]_ The node has not seen another valid `SignedExecutionPayloadEnvelope` for this block root from this builder. - -Let `block` be the block with `envelope.beacon_block_root`. + +Let `block` be the block with `envelope.beacon_block_root`. Let `header` alias `block.body.signed_execution_payload_header.message` (notice that this can be obtained from the `state.signed_execution_payload_header`) -- _[REJECT]_ `block` passes validation. -- _[REJECT]_ `envelope.builder_index == header.builder_index` -- if `envelope.payload_withheld == False` then +- _[REJECT]_ `block` passes validation. +- _[REJECT]_ `envelope.builder_index == header.builder_index` +- if `envelope.payload_withheld == False` then - _[REJECT]_ `payload.block_hash == header.block_hash` - _[REJECT]_ The builder signature, `signed_execution_payload_envelope.signature`, is valid with respect to the builder's public key. @@ -163,14 +168,14 @@ This topic is used to propagate signed payload attestation message. The following validations MUST pass before forwarding the `payload_attestation_message` on the network, assuming the alias `data = payload_attestation_message.data`: -- _[IGNORE]_ The message's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `data.slot == current_slot`. -- _[REJECT]_ The message's payload status is a valid status, i.e. `data.payload_status < PAYLOAD_INVALID_STATUS`. -- _[IGNORE]_ The `payload_attestation_message` is the first valid message received from the validator with index `payload_attestation_message.validate_index`. -- _[IGNORE]_ The message's block `data.beacon_block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). -- _[REJECT]_ The message's block `data.beacon_block_root` passes validation. -- _[REJECT]_ The message's validator index is within the payload committee in `get_ptc(state, data.slot)`. The `state` is the head state corresponding to processing the block up to the current slot as determined by the fork choice. -- _[REJECT]_ The message's signature of `payload_attestation_message.signature` is valid with respect to the validator index. - +- _[IGNORE]_ The message's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `data.slot == current_slot`. +- _[REJECT]_ The message's payload status is a valid status, i.e. `data.payload_status < PAYLOAD_INVALID_STATUS`. +- _[IGNORE]_ The `payload_attestation_message` is the first valid message received from the validator with index `payload_attestation_message.validate_index`. +- _[IGNORE]_ The message's block `data.beacon_block_root` has been seen (via both gossip and non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after). +- _[REJECT]_ The message's block `data.beacon_block_root` passes validation. +- _[REJECT]_ The message's validator index is within the payload committee in `get_ptc(state, data.slot)`. The `state` is the head state corresponding to processing the block up to the current slot as determined by the fork choice. +- _[REJECT]_ The message's signature of `payload_attestation_message.signature` is valid with respect to the validator index. + ###### `execution_payload_header` This topic is used to propagate signed bids as `SignedExecutionPayloadHeader`. @@ -182,8 +187,8 @@ The following validations MUST pass before forwarding the `signed_execution_payl - _[REJECT]_ The signed builder bid, `header.builder_index` is a valid, active, and non-slashed builder index in state. - _[IGNORE]_ The signed builder bid value, `header.value`, is less or equal than the builder's balance in state. i.e. `MIN_BUILDER_BALANCE + header.value < state.builder_balances[header.builder_index]`. - _[IGNORE]_ `header.parent_block_hash` is the block hash of a known execution payload in fork choice. -_ _[IGNORE]_ `header.parent_block_root` is the hash tree root of a known beacon block in fork choice. -- _[IGNORE]_ `header.slot` is the current slot or the next slot. +_ _[IGNORE]_ `header.parent_block_root` is the hash tree root of a known beacon block in fork choice. +- _[IGNORE]_ `header.slot` is the current slot or the next slot. - _[REJECT]_ The builder signature, `signed_execution_payload_header_envelope.signature`, is valid with respect to the `header_envelope.builder_index`. ### The Req/Resp domain @@ -220,7 +225,7 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: | `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | | `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | | `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | -| `EIP7732_FORK_VERSION` | `eip7732.SignedBeaconBlock` | +| `EIP7732_FORK_VERSION` | `eip7732.SignedBeaconBlock` | ##### BlobSidecarsByRoot v2 diff --git a/specs/_features/eip7732/validator.md b/specs/_features/eip7732/validator.md index 6a6cbeecef..3a4d4e93ea 100644 --- a/specs/_features/eip7732/validator.md +++ b/specs/_features/eip7732/validator.md @@ -1,10 +1,10 @@ # EIP-7732 -- Honest Validator -This document represents the changes and additions to the Honest validator guide included in the EIP-7732 fork. +This document represents the changes and additions to the Honest validator guide included in the EIP-7732 fork. -**Table of Contents** +**Table of Contents** - [Validator assignment](#validator-assignment) - [Lookahead](#lookahead) @@ -33,7 +33,7 @@ def get_ptc_assignment( validator_index: ValidatorIndex) -> Optional[Slot]: """ Returns the slot during the requested epoch in which the validator with index `validator_index` - is a member of the PTC. Returns None if no assignment is found. + is a member of the PTC. Returns None if no assignment is found. """ next_epoch = Epoch(get_current_epoch(state) + 1) assert epoch <= next_epoch @@ -49,22 +49,22 @@ def get_ptc_assignment( [New in EIP-7732] -`get_ptc_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments by noting their assigned PTC slot. +`get_ptc_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments by noting their assigned PTC slot. ## Beacon chain responsibilities All validator responsibilities remain unchanged other than the following: -- Proposers are no longer required to broadcast `BlobSidecar` objects, as this becomes a builder's duty. -- Some validators are selected per slot to become PTC members, these validators must broadcast `PayloadAttestationMessage` objects during the assigned slot before the deadline of `3 * SECONDS_PER_SLOT // INTERVALS_PER_SLOT` seconds into the slot. +- Proposers are no longer required to broadcast `BlobSidecar` objects, as this becomes a builder's duty. +- Some validators are selected per slot to become PTC members, these validators must broadcast `PayloadAttestationMessage` objects during the assigned slot before the deadline of `3 * SECONDS_PER_SLOT // INTERVALS_PER_SLOT` seconds into the slot. ### Attestation -Attestation duties are not changed for validators, however the attestation deadline is implicitly changed by the change in `INTERVALS_PER_SLOT`. +Attestation duties are not changed for validators, however the attestation deadline is implicitly changed by the change in `INTERVALS_PER_SLOT`. ### Sync Committee participations -Sync committee duties are not changed for validators, however the submission deadline is implicitly changed by the change in `INTERVALS_PER_SLOT`. +Sync committee duties are not changed for validators, however the submission deadline is implicitly changed by the change in `INTERVALS_PER_SLOT`. ### Block proposal @@ -74,32 +74,32 @@ Validators are still expected to propose `SignedBeaconBlock` at the beginning of #### Constructing the new `signed_execution_payload_header` field in `BeaconBlockBody` To obtain `signed_execution_payload_header`, a block proposer building a block on top of a `state` must take the following actions: -* Listen to the `execution_payload_header` gossip global topic and save an accepted `signed_execution_payload_header` from a builder. Proposer MAY obtain these signed messages by other off-protocol means. -* The `signed_execution_payload_header` must satisfy the verification conditions found in `process_execution_payload_header`, that is +* Listen to the `execution_payload_header` gossip global topic and save an accepted `signed_execution_payload_header` from a builder. Proposer MAY obtain these signed messages by other off-protocol means. +* The `signed_execution_payload_header` must satisfy the verification conditions found in `process_execution_payload_header`, that is - The header signature must be valid - The builder balance can cover the header value - The header slot is for the proposal block slot - - The header parent block hash equals the state's `latest_block_hash`. + - The header parent block hash equals the state's `latest_block_hash`. - The header parent block root equals the current block's `parent_root`. * Select one bid and set `body.signed_execution_payload_header = signed_execution_payload_header` #### Constructing the new `payload_attestations` field in `BeaconBlockBody` -Up to `MAX_PAYLOAD_ATTESTATIONS`, aggregate payload attestations can be included in the block. The validator will have to -* Listen to the `payload_attestation_message` gossip global topic +Up to `MAX_PAYLOAD_ATTESTATIONS`, aggregate payload attestations can be included in the block. The validator will have to +* Listen to the `payload_attestation_message` gossip global topic * The payload attestations added must satisfy the verification conditions found in payload attestation gossip validation and payload attestation processing. This means - The `data.beacon_block_root` corresponds to `block.parent_root`. - - The slot of the parent block is exactly one slot before the proposing slot. - - The signature of the payload attestation data message verifies correctly. -* The proposer needs to aggregate all payload attestations with the same data into a given `PayloadAttestation` object. For this it needs to fill the `aggregation_bits` field by using the relative position of the validator indices with respect to the PTC that is obtained from `get_ptc(state, block_slot - 1)`. -* The proposer should only include payload attestations that are consistent with the current block they are proposing. That is, if the previous block had a payload, they should only include attestations with `payload_status = PAYLOAD_PRESENT`. Proposers are penalized for attestations that are not-consistent with their view. + - The slot of the parent block is exactly one slot before the proposing slot. + - The signature of the payload attestation data message verifies correctly. +* The proposer needs to aggregate all payload attestations with the same data into a given `PayloadAttestation` object. For this it needs to fill the `aggregation_bits` field by using the relative position of the validator indices with respect to the PTC that is obtained from `get_ptc(state, block_slot - 1)`. +* The proposer should only include payload attestations that are consistent with the current block they are proposing. That is, if the previous block had a payload, they should only include attestations with `payload_status = PAYLOAD_PRESENT`. Proposers are penalized for attestations that are not-consistent with their view. #### Blob sidecars The blob sidecars are no longer broadcast by the validator, and thus their construction is not necessary. This deprecates the corresponding sections from the honest validator guide in the Electra fork, moving them, albeit with some modifications, to the [honest Builder guide](./builder.md) ### Payload timeliness attestation -Some validators are selected to submit payload timeliness attestations. Validators should call `get_ptc_assignment` at the beginning of an epoch to be prepared to submit their PTC attestations during the next epoch. +Some validators are selected to submit payload timeliness attestations. Validators should call `get_ptc_assignment` at the beginning of an epoch to be prepared to submit their PTC attestations during the next epoch. A validator should create and broadcast the `payload_attestation_message` to the global execution attestation subnet not after `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` seconds since the start of `slot` @@ -109,9 +109,9 @@ If a validator is in the payload attestation committee for the current slot (as according to the logic in `get_payload_attestation_message` below and broadcast it not after `SECONDS_PER_SLOT * 3 / INTERVALS_PER_SLOT` seconds since the start of the slot, to the global `payload_attestation_message` pubsub topic. The validator creates `payload_attestation_message` as follows: -* If the validator has not seen any beacon block for the assigned slot, do not submit a payload attestation. It will be ignored anyway. +* If the validator has not seen any beacon block for the assigned slot, do not submit a payload attestation. It will be ignored anyway. * Set `data.beacon_block_root` be the HTR of the beacon block seen for the assigned slot -* Set `data.slot` to be the assigned slot. +* Set `data.slot` to be the assigned slot. * Set `data.payload_status` as follows - If a `SignedExecutionPayloadEnvelope` has been seen referencing the block `data.beacon_block_root` and the envelope has `payload_withheld = False`, set to `PAYLOAD_PRESENT`. - If a `SignedExecutionPayloadEnvelope` has been seen referencing the block `data.beacon_block_root` and the envelope has `payload_withheld = True`, set to `PAYLOAD_WITHHELD`. @@ -119,7 +119,7 @@ The validator creates `payload_attestation_message` as follows: * Set `payload_attestation_message.validator_index = validator_index` where `validator_index` is the validator chosen to submit. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the payload timeliness attestation. * Sign the `payload_attestation_message.data` using the helper `get_payload_attestation_message_signature`. -Notice that the attester only signs the `PayloadAttestationData` and not the `validator_index` field in the message. Proposers need to aggregate these attestations as described above. +Notice that the attester only signs the `PayloadAttestationData` and not the `validator_index` field in the message. Proposers need to aggregate these attestations as described above. ```python def get_payload_attestation_message_signature( @@ -129,6 +129,6 @@ def get_payload_attestation_message_signature( return bls.Sign(privkey, signing_root) ``` -**Remark** Validators do not need to check the full validity of the `ExecutionPayload` contained in within the envelope, but the checks in the [P2P guide](./p2p-interface.md) should pass for the `SignedExecutionPayloadEnvelope`. +**Remark** Validators do not need to check the full validity of the `ExecutionPayload` contained in within the envelope, but the checks in the [P2P guide](./p2p-interface.md) should pass for the `SignedExecutionPayloadEnvelope`. diff --git a/specs/_features/sharding/beacon-chain.md b/specs/_features/sharding/beacon-chain.md index e67bc09ed6..35b6776028 100644 --- a/specs/_features/sharding/beacon-chain.md +++ b/specs/_features/sharding/beacon-chain.md @@ -82,7 +82,7 @@ The following values are (non-configurable) constants used throughout the specif | - | - | - | | `MAX_SHARDS` | `uint64(2**12)` (= 4,096) | Theoretical max shard count (used to determine data structure sizes) | | `ACTIVE_SHARDS` | `uint64(2**8)` (= 256) | Initial shard count | -| `MAX_PROPOSER_BLOCKS_BETWEEN_BUILDER_BLOCKS` | `uint64(2**4)` (= 16) | TODO: Need to define what happens if there were more blocks without builder blocks | +| `MAX_PROPOSER_BLOCKS_BETWEEN_BUILDER_BLOCKS` | `uint64(2**4)` (= 16) | TODO: Need to define what happens if there were more blocks without builder blocks | ### Time parameters @@ -100,7 +100,7 @@ With the introduction of builder blocks the number of slots per epoch is doubled ## Configuration -Note: Some preset variables may become run-time configurable for testnets, but default to a preset while the spec is unstable. +Note: Some preset variables may become run-time configurable for testnets, but default to a preset while the spec is unstable. E.g. `ACTIVE_SHARDS` and `SAMPLES_PER_BLOB`. ### Time parameters @@ -129,12 +129,12 @@ class BuilderBlockBid(Container): bid: Gwei # Block builder bid paid to proposer validator_index: ValidatorIndex # Validator index for this bid - + # Block builders use an Eth1 address -- need signature as # block bid and data gas base fees will be charged to this address signature_y_parity: bool signature_r: uint256 - signature_s: uint256 + signature_s: uint256 ``` #### `BuilderBlockBidWithRecipientAddress` @@ -156,7 +156,7 @@ class ShardedCommitmentsContainer(Container): # The sizes of the blocks encoded in the commitments (last builder and all beacon blocks since) included_block_sizes: List[uint64, MAX_PROPOSER_BLOCKS_BETWEEN_BUILDER_BLOCKS + 1] - + # Number of commitments that are for sharded data (no blocks) included_sharded_data_commitments: uint64 @@ -192,7 +192,7 @@ class BeaconState(bellatrix.BeaconState): class BuilderBlockData(Container): execution_payload: ExecutionPayload sharded_commitments_container: ShardedCommitmentsContainer -``` +``` #### `BeaconBlockBody` diff --git a/specs/_features/sharding/polynomial-commitments.md b/specs/_features/sharding/polynomial-commitments.md index f52d950664..865328597e 100644 --- a/specs/_features/sharding/polynomial-commitments.md +++ b/specs/_features/sharding/polynomial-commitments.md @@ -203,7 +203,7 @@ def low_degree_check(commitments: List[KZGCommitment]): coefs.append( - (r_to_K - 1) * bls_modular_inverse(K * roots[i * (K - 1) % K] * (r - roots[i])) % BLS_MODULUS) for i in range(d + 1): coefs[i] = (coefs[i] + B(r) * bls_modular_inverse(Bprime(r) * (r - roots[i]))) % BLS_MODULUS - + assert elliptic_curve_lincomb(commitments, coefs) == bls.inf_G1() ``` @@ -279,7 +279,7 @@ def interpolate_polynomial(xs: List[BLSFieldElement], ys: List[BLSFieldElement]) summand, [weight_adjustment, ((BLS_MODULUS - weight_adjustment) * xs[i])] ) r = add_polynomials(r, summand) - + return r ``` @@ -300,7 +300,7 @@ def evaluate_polynomial_in_evaluation_form(poly: BLSPolynomialByEvaluations, x: return r def Aprime(z): - return field_elements_per_blob * pow(z, field_elements_per_blob - 1, BLS_MODULUS) + return field_elements_per_blob * pow(z, field_elements_per_blob - 1, BLS_MODULUS) r = 0 inverses = [bls_modular_inverse(z - x) for z in roots] @@ -312,7 +312,7 @@ def evaluate_polynomial_in_evaluation_form(poly: BLSPolynomialByEvaluations, x: ## KZG Operations -We are using the KZG10 polynomial commitment scheme (Kate, Zaverucha and Goldberg, 2010: https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf). +We are using the KZG10 polynomial commitment scheme (Kate, Zaverucha and Goldberg, 2010: https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf). ### Elliptic curve helper functions @@ -387,7 +387,7 @@ def verify_kzg_multiproof(commitment: KZGCommitment, ```python def verify_degree_proof(commitment: KZGCommitment, degree_bound: uint64, proof: KZGCommitment): """ - Verifies that the commitment is of polynomial degree < degree_bound. + Verifies that the commitment is of polynomial degree < degree_bound. """ assert ( diff --git a/specs/_features/whisk/beacon-chain.md b/specs/_features/whisk/beacon-chain.md index 9c2cb389a3..de8051ffeb 100644 --- a/specs/_features/whisk/beacon-chain.md +++ b/specs/_features/whisk/beacon-chain.md @@ -25,7 +25,6 @@ - [`BeaconBlockBody`](#beaconblockbody) - [Deposits](#deposits) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) -- [Testing](#testing) @@ -96,7 +95,7 @@ def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement: | --------------------- | ------------------------------------------------------------------------------- | | `BLS_G1_GENERATOR` | `BLSG1Point('0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb') # noqa: E501` | | `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | -| `CURDLEPROOFS_CRS` | TBD | +| `CURDLEPROOFS_CRS` | TBD | ### Curdleproofs and opening proofs @@ -423,7 +422,7 @@ def add_validator_to_registry(state: BeaconState, # [New in Whisk] k = get_unique_whisk_k(state, ValidatorIndex(len(state.validators) - 1)) state.whisk_trackers.append(get_initial_tracker(k)) - state.whisk_k_commitments.append(get_k_commitment(k)) + state.whisk_k_commitments.append(get_k_commitment(k)) ``` ### `get_beacon_proposer_index` @@ -436,25 +435,3 @@ def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: assert state.latest_block_header.slot == state.slot # sanity check `process_block_header` has been called return state.latest_block_header.proposer_index ``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified purely for Whisk testing. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - state_capella = capella.initialize_beacon_state_from_eth1( - eth1_block_hash, - eth1_timestamp, - deposits, - execution_payload_header, - ) - state = upgrade_to_whisk(state_capella) - state.fork.previous_version = WHISK_FORK_VERSION - state.fork.current_version = WHISK_FORK_VERSION - return state -``` diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index 4bad3b556e..0b7e47a8be 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -54,7 +54,6 @@ - [Slashings](#slashings) - [Participation flags updates](#participation-flags-updates) - [Sync committee updates](#sync-committee-updates) -- [Initialize state for pure Altair testnets and test vectors](#initialize-state-for-pure-altair-testnets-and-test-vectors) @@ -672,52 +671,3 @@ def process_sync_committee_updates(state: BeaconState) -> None: state.current_sync_committee = state.next_sync_committee state.next_sync_committee = get_next_sync_committee(state) ``` - -## Initialize state for pure Altair testnets and test vectors - -This helper function is only for initializing the state for pure Altair testnets and tests. - -*Note*: The function `initialize_beacon_state_from_eth1` is modified: (1) using `ALTAIR_FORK_VERSION` as the previous and current fork version, (2) utilizing the Altair `BeaconBlockBody` when constructing the initial `latest_block_header`, and (3) adding initial sync committees. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit]) -> BeaconState: - fork = Fork( - previous_version=ALTAIR_FORK_VERSION, # [Modified in Altair] for testing only - current_version=ALTAIR_FORK_VERSION, # [Modified in Altair] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # [New in Altair] Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - return state -``` diff --git a/specs/altair/light-client/p2p-interface.md b/specs/altair/light-client/p2p-interface.md index c33d887a50..e1fe7487db 100644 --- a/specs/altair/light-client/p2p-interface.md +++ b/specs/altair/light-client/p2p-interface.md @@ -71,7 +71,7 @@ For light clients, the following validations MUST additionally pass before forwa Light clients SHOULD call `process_light_client_finality_update` even if the message is ignored. -The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(finality_update.attested_header.beacon.slot))`. +The gossip `ForkDigestValue` is determined based on `compute_fork_version(compute_epoch_at_slot(finality_update.attested_header.beacon.slot))`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -99,7 +99,7 @@ For light clients, the following validations MUST additionally pass before forwa Light clients SHOULD call `process_light_client_optimistic_update` even if the message is ignored. -The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(optimistic_update.attested_header.beacon.slot))`. +The gossip `ForkDigestValue` is determined based on `compute_fork_version(compute_epoch_at_slot(optimistic_update.attested_header.beacon.slot))`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: diff --git a/specs/altair/p2p-interface.md b/specs/altair/p2p-interface.md index 5b0c938df7..f70e4e9b15 100644 --- a/specs/altair/p2p-interface.md +++ b/specs/altair/p2p-interface.md @@ -1,18 +1,12 @@ # Altair -- Networking -This document contains the networking specification for Altair. -This document should be viewed as additive to the [document from Phase 0](../phase0/p2p-interface.md) and will be referred to as the "Phase 0 document" hereafter. -Readers should understand the Phase 0 document and use it as a basis to understand the changes outlined in this document. - -Altair adds new messages, topics and data to the Req-Resp, Gossip and Discovery domain. Some Phase 0 features will be deprecated, but not removed immediately. - - ## Table of contents +- [Introduction](#introduction) - [Modifications in Altair](#modifications-in-altair) - [MetaData](#metadata) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) @@ -39,6 +33,14 @@ Altair adds new messages, topics and data to the Req-Resp, Gossip and Discovery +## Introduction + +This document contains the networking specification for Altair. +This document should be viewed as additive to the [document from Phase 0](../phase0/p2p-interface.md) and will be referred to as the "Phase 0 document" hereafter. +Readers should understand the Phase 0 document and use it as a basis to understand the changes outlined in this document. + +Altair adds new messages, topics and data to the Req-Resp, Gossip and Discovery domain. Some Phase 0 features will be deprecated, but not removed immediately. + ## Modifications in Altair ### MetaData diff --git a/specs/bellatrix/beacon-chain.md b/specs/bellatrix/beacon-chain.md index 51d570fe2d..3a6c6a4b86 100644 --- a/specs/bellatrix/beacon-chain.md +++ b/specs/bellatrix/beacon-chain.md @@ -44,7 +44,6 @@ - [`process_execution_payload`](#process_execution_payload) - [Epoch processing](#epoch-processing) - [Slashings](#slashings) -- [Testing](#testing) @@ -441,62 +440,3 @@ def process_slashings(state: BeaconState) -> None: penalty = penalty_numerator // total_balance * increment decrease_balance(state, ValidatorIndex(index), penalty) ``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Bellatrix testing only. -Modifications include: -1. Use `BELLATRIX_FORK_VERSION` as the previous and current fork version. -2. Utilize the Bellatrix `BeaconBlockBody` when constructing the initial `latest_block_header`. -3. Initialize `latest_execution_payload_header`. - If `execution_payload_header == ExecutionPayloadHeader()`, then the Merge has not yet occurred. - Else, the Merge starts from genesis and the transition is incomplete. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=BELLATRIX_FORK_VERSION, # [Modified in Bellatrix] for testing only - current_version=BELLATRIX_FORK_VERSION, # [Modified in Bellatrix] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # [New in Bellatrix] Initialize the execution payload header - # If empty, will initialize a chain that has not yet gone through the Merge transition - state.latest_execution_payload_header = execution_payload_header - - return state -``` diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md index 617f6f1fc7..1f4c815660 100644 --- a/specs/bellatrix/p2p-interface.md +++ b/specs/bellatrix/p2p-interface.md @@ -1,17 +1,12 @@ # Bellatrix -- Networking -This document contains the networking specification for the Bellatrix. - -The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. This document should be viewed as additive to the documents from [Phase 0](../phase0/p2p-interface.md) and from [Altair](../altair/p2p-interface.md) -and will be referred to as the "Phase 0 document" and "Altair document" respectively, hereafter. -Readers should understand the Phase 0 and Altair documents and use them as a basis to understand the changes outlined in this document. - ## Table of contents + - [Introduction](#introduction) - [Modifications in Bellatrix](#modifications-in-bellatrix) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) @@ -32,6 +27,14 @@ Readers should understand the Phase 0 and Altair documents and use them as a bas +## Introduction + +This document contains the networking specification for Bellatrix. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. This document should be viewed as additive to the documents from [Phase 0](../phase0/p2p-interface.md) and from [Altair](../altair/p2p-interface.md) +and will be referred to as the "Phase 0 document" and "Altair document" respectively, hereafter. +Readers should understand the Phase 0 and Altair documents and use them as a basis to understand the changes outlined in this document. + ## Modifications in Bellatrix ### The gossip domain: gossipsub @@ -146,7 +149,7 @@ Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: With the addition of `ExecutionPayload` to `BeaconBlock`s, there is a dynamic field -- `transactions` -- which can validly exceed the `GOSSIP_MAX_SIZE` limit (1 MiB) put in -place at Phase 0, so GOSSIP_MAX_SIZE has increased to 10 Mib on the network. +place at Phase 0, so GOSSIP_MAX_SIZE has increased to 10 Mib on the network. At the `GAS_LIMIT` (~30M) currently seen on mainnet in 2021, a single transaction filled entirely with data at a cost of 16 gas per byte can create a valid `ExecutionPayload` of ~2 MiB. Thus we need a size limit to at least account for diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index 103530bf8b..54ac8a6782 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -38,7 +38,6 @@ - [Modified `process_execution_payload`](#modified-process_execution_payload) - [Modified `process_operations`](#modified-process_operations) - [New `process_bls_to_execution_change`](#new-process_bls_to_execution_change) -- [Testing](#testing) @@ -484,58 +483,3 @@ def process_bls_to_execution_change(state: BeaconState, + address_change.to_execution_address ) ``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Capella testing only. -Modifications include: -1. Use `CAPELLA_FORK_VERSION` as the previous and current fork version. -2. Utilize the Capella `BeaconBlockBody` when constructing the initial `latest_block_header`. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=CAPELLA_FORK_VERSION, # [Modified in Capella] for testing only - current_version=CAPELLA_FORK_VERSION, # [Modified in Capella] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # Initialize the execution payload header - state.latest_execution_payload_header = execution_payload_header - - return state -``` diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md index a71b6479f1..25b2fb3b99 100644 --- a/specs/capella/p2p-interface.md +++ b/specs/capella/p2p-interface.md @@ -1,15 +1,12 @@ # Capella -- Networking -This document contains the networking specification for Capella. - -The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. - ### Table of contents +- [Introduction](#introduction) - [Modifications in Capella](#modifications-in-capella) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) @@ -25,6 +22,11 @@ The specification of these changes continues in the same format as the network s +## Introduction + +This document contains the networking specification for Capella. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. ## Modifications in Capella diff --git a/specs/capella/validator.md b/specs/capella/validator.md index 6f30ea6e4d..624a895b9b 100644 --- a/specs/capella/validator.md +++ b/specs/capella/validator.md @@ -110,7 +110,7 @@ Validator balances are withdrawn periodically via an automatic process. For exit There is one prerequisite for this automated process: the validator's withdrawal credentials pointing to an execution layer address, i.e. having an `ETH1_ADDRESS_WITHDRAWAL_PREFIX`. -If a validator has a `BLS_WITHDRAWAL_PREFIX` withdrawal credential prefix, to participate in withdrawals the validator must +If a validator has a `BLS_WITHDRAWAL_PREFIX` withdrawal credential prefix, to participate in withdrawals the validator must create a one-time message to change their withdrawal credential from the version authenticated with a BLS key to the version compatible with the execution layer. This message -- a `BLSToExecutionChange` -- is available starting in Capella diff --git a/specs/deneb/beacon-chain.md b/specs/deneb/beacon-chain.md index e1ac44d66d..43360f8b3e 100644 --- a/specs/deneb/beacon-chain.md +++ b/specs/deneb/beacon-chain.md @@ -42,7 +42,6 @@ - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) - [Epoch processing](#epoch-processing) - [Registry updates](#registry-updates) -- [Testing](#testing) @@ -466,59 +465,3 @@ def process_registry_updates(state: BeaconState) -> None: validator = state.validators[index] validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) ``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Deneb testing only. - -The `BeaconState` initialization is unchanged, except for the use of the updated `deneb.BeaconBlockBody` type -when initializing the first body-root. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=DENEB_FORK_VERSION, # [Modified in Deneb] for testing only - current_version=DENEB_FORK_VERSION, # [Modified in Deneb] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - if validator.effective_balance == MAX_EFFECTIVE_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # Initialize the execution payload header - # If empty, will initialize a chain that has not yet gone through the Merge transition - state.latest_execution_payload_header = execution_payload_header - - return state -``` diff --git a/specs/deneb/p2p-interface.md b/specs/deneb/p2p-interface.md index aa63ebe87e..5f71bc854a 100644 --- a/specs/deneb/p2p-interface.md +++ b/specs/deneb/p2p-interface.md @@ -1,15 +1,12 @@ # Deneb -- Networking -This document contains the consensus-layer networking specification for Deneb. - -The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. - ## Table of contents +- [Introduction](#introduction) - [Modifications in Deneb](#modifications-in-deneb) - [Constant](#constant) - [Preset](#preset) @@ -42,6 +39,12 @@ The specification of these changes continues in the same format as the network s +## Introduction + +This document contains the consensus-layer networking specification for Deneb. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + ## Modifications in Deneb ### Constant @@ -188,6 +191,16 @@ The following validations MUST pass before forwarding the `blob_sidecar` on the - _[REJECT]_ The sidecar is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `block_header.parent_root`/`block_header.slot`). If the `proposer_index` cannot immediately be verified against the expected shuffling, the sidecar MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case _do not_ `REJECT`, instead `IGNORE` this message. +The gossip `ForkDigestValue` is determined based on `compute_fork_version(compute_epoch_at_slot(blob_sidecar.signed_block_header.message.slot))`. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------------|---------------------| +| `DENEB_FORK_VERSION` and later | `deneb.BlobSidecar` | + ##### Attestation subnets ###### `beacon_attestation_{subnet_id}` @@ -242,7 +255,7 @@ No more than `MAX_REQUEST_BLOCKS_DENEB` may be requested at a time. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: -[1]: # (eth2spec: skip) +[0]: # (eth2spec: skip) | `fork_version` | Chunk SSZ type | |--------------------------|-------------------------------| @@ -264,14 +277,6 @@ Clients SHOULD NOT respond with blocks that fail the beacon chain state transiti *[New in Deneb:EIP4844]* -The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: - -[1]: # (eth2spec: skip) - -| `fork_version` | Chunk SSZ type | -|--------------------------|-------------------------------| -| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` | - Request Content: ``` @@ -310,6 +315,16 @@ Clients SHOULD include a sidecar in the response as soon as it passes the gossip Clients SHOULD NOT respond with sidecars related to blocks that fail gossip validation rules. Clients SHOULD NOT respond with sidecars related to blocks that fail the beacon chain state transition +For each `response_chunk`, a `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(blob_sidecar.signed_block_header.message.slot))` is used to select the fork namespace of the Response type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------------|---------------------| +| `DENEB_FORK_VERSION` and later | `deneb.BlobSidecar` | + ###### Blob retrieval via local execution layer client In addition to `BlobSidecarsByRoot` requests, recent blobs MAY be retrieved by querying the Execution Layer (i.e. via `engine_getBlobsV1`). @@ -325,14 +340,6 @@ When clients use the local execution layer to retrieve blobs, they MUST behave a *[New in Deneb:EIP4844]* -The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: - -[1]: # (eth2spec: skip) - -| `fork_version` | Chunk SSZ type | -|--------------------------|-------------------------------| -| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` | - Request Content: ``` ( @@ -399,6 +406,16 @@ Clients MUST respond with blob sidecars that are consistent from a single chain After the initial blob sidecar, clients MAY stop in the process of responding if their fork choice changes the view of the chain in the context of the request. +For each `response_chunk`, a `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(blob_sidecar.signed_block_header.message.slot))` is used to select the fork namespace of the Response type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[0]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|--------------------------------|---------------------| +| `DENEB_FORK_VERSION` and later | `deneb.BlobSidecar` | + ## Design decision rationale ### Why are blobs relayed as a sidecar, separate from beacon blocks? diff --git a/specs/deneb/polynomial-commitments.md b/specs/deneb/polynomial-commitments.md index 84678f8a61..f73b17309b 100644 --- a/specs/deneb/polynomial-commitments.md +++ b/specs/deneb/polynomial-commitments.md @@ -438,7 +438,7 @@ def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment], for commitment, y in zip(commitments, ys)] C_minus_y_as_KZGCommitments = [KZGCommitment(bls.G1_to_bytes48(x)) for x in C_minus_ys] C_minus_y_lincomb = g1_lincomb(C_minus_y_as_KZGCommitments, r_powers) - + return bls.pairing_check([ [bls.bytes48_to_G1(proof_lincomb), bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[1]))], [bls.add(bls.bytes48_to_G1(C_minus_y_lincomb), bls.bytes48_to_G1(proof_z_lincomb)), bls.G2()] diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index 6e7b536fbc..c366b9c3f9 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -22,6 +22,7 @@ - [Withdrawals processing](#withdrawals-processing) - [Pending deposits processing](#pending-deposits-processing) - [Configuration](#configuration) + - [Execution](#execution-1) - [Validator cycle](#validator-cycle) - [Containers](#containers) - [New containers](#new-containers) @@ -31,8 +32,8 @@ - [`DepositRequest`](#depositrequest) - [`WithdrawalRequest`](#withdrawalrequest) - [`ConsolidationRequest`](#consolidationrequest) - - [`SingleAttestation`](#singleattestation) - [`ExecutionRequests`](#executionrequests) + - [`SingleAttestation`](#singleattestation) - [Modified Containers](#modified-containers) - [`AttesterSlashing`](#attesterslashing) - [`BeaconBlockBody`](#beaconblockbody) @@ -79,6 +80,7 @@ - [Request data](#request-data) - [Modified `NewPayloadRequest`](#modified-newpayloadrequest) - [Engine APIs](#engine-apis) + - [Modified `is_valid_block_hash`](#modified-is_valid_block_hash) - [Modified `notify_new_payload`](#modified-notify_new_payload) - [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload) - [Block processing](#block-processing) @@ -107,7 +109,6 @@ - [Execution layer consolidation requests](#execution-layer-consolidation-requests) - [New `is_valid_switch_to_compounding_request`](#new-is_valid_switch_to_compounding_request) - [New `process_consolidation_request`](#new-process_consolidation_request) -- [Testing](#testing) @@ -119,6 +120,7 @@ Electra is a consensus-layer upgrade containing a number of features. Including: * [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable exits * [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE * [EIP-7549](https://eips.ethereum.org/EIPS/eip-7549): Move committee index outside Attestation +* [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691): Blob throughput increase *Note:* This specification is built upon [Deneb](../deneb/beacon-chain.md) and is under active development. @@ -130,14 +132,14 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Description | | - | - | - | -| `UNSET_DEPOSIT_REQUESTS_START_INDEX` | `uint64(2**64 - 1)` | *[New in Electra:EIP6110]* | -| `FULL_EXIT_REQUEST_AMOUNT` | `uint64(0)` | *[New in Electra:EIP7002]* | +| `UNSET_DEPOSIT_REQUESTS_START_INDEX` | `uint64(2**64 - 1)` | *[New in Electra:EIP6110]* Value which indicates no start index has been assigned | +| `FULL_EXIT_REQUEST_AMOUNT` | `uint64(0)` | *[New in Electra:EIP7002]* Withdrawal amount used to signal a full validator exit | ### Withdrawal prefixes -| Name | Value | -| - | - | -| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` | +| Name | Value | Description | +| - | - | - | +| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` | *[New in Electra:EIP7251]* Withdrawal credential prefix for a compounding validator | ### Execution layer triggered requests @@ -151,17 +153,17 @@ The following values are (non-configurable) constants used throughout the specif ### Gwei values -| Name | Value | -| - | - | -| `MIN_ACTIVATION_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) | -| `MAX_EFFECTIVE_BALANCE_ELECTRA` | `Gwei(2**11 * 10**9)` (= 2048,000,000,000) | +| Name | Value | Description | +| - | - | - | +| `MIN_ACTIVATION_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) | *[New in Electra:EIP7251]* Minimum balance for a validator to become active | +| `MAX_EFFECTIVE_BALANCE_ELECTRA` | `Gwei(2**11 * 10**9)` (= 2048,000,000,000) | *[New in Electra:EIP7251]* Maximum effective balance for a compounding validator | ### Rewards and penalties | Name | Value | | - | - | -| `MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) | -| `WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) | +| `MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) | +| `WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA` | `uint64(2**12)` (= 4,096) | ### State list lengths @@ -175,16 +177,16 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | | - | - | -| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | *[New in Electra:EIP7549]* | -| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | *[New in Electra:EIP7549]* | +| `MAX_ATTESTER_SLASHINGS_ELECTRA` | `2**0` (= 1) | +| `MAX_ATTESTATIONS_ELECTRA` | `2**3` (= 8) | ### Execution | Name | Value | Description | | - | - | - | -| `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of deposit receipts allowed in each payload | +| `MAX_DEPOSIT_REQUESTS_PER_PAYLOAD` | `uint64(2**13)` (= 8,192) | *[New in Electra:EIP6110]* Maximum number of execution layer deposit requests in each payload | | `MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD` | `uint64(2**4)` (= 16)| *[New in Electra:EIP7002]* Maximum number of execution layer withdrawal requests in each payload | -| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(1)` (= 1) | *[New in Electra:EIP7251]* Maximum number of execution layer consolidation requests in each payload | +| `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` | `uint64(2**1)` (= 2) | *[New in Electra:EIP7251]* Maximum number of execution layer consolidation requests in each payload | ### Withdrawals processing @@ -200,6 +202,12 @@ The following values are (non-configurable) constants used throughout the specif ## Configuration +### Execution + +| Name | Value | Description | +| - | - | - | +| `MAX_BLOBS_PER_BLOCK_ELECTRA` | `uint64(9)` | *[New in Electra:EIP7691]* Maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` | + ### Validator cycle | Name | Value | @@ -230,7 +238,7 @@ class PendingDeposit(Container): ```python class PendingPartialWithdrawal(Container): - index: ValidatorIndex + validator_index: ValidatorIndex amount: Gwei withdrawable_epoch: Epoch ``` @@ -280,16 +288,6 @@ class ConsolidationRequest(Container): target_pubkey: BLSPubkey ``` -#### `SingleAttestation` - -```python -class SingleAttestation(Container): - committee_index: CommitteeIndex - attester_index: ValidatorIndex - data: AttestationData - signature: BLSSignature -``` - #### `ExecutionRequests` *Note*: This container holds requests from the execution layer that are received in [ @@ -303,6 +301,16 @@ class ExecutionRequests(Container): consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] # [New in Electra:EIP7251] ``` +#### `SingleAttestation` + +```python +class SingleAttestation(Container): + committee_index: CommitteeIndex + attester_index: ValidatorIndex + data: AttestationData + signature: BLSSignature +``` + ### Modified Containers #### `AttesterSlashing` @@ -419,7 +427,7 @@ class BeaconState(Container): #### Modified `compute_proposer_index` -*Note*: The function `compute_proposer_index` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA`. +*Note*: The function `compute_proposer_index` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` and to use a 16-bit random value instead of an 8-bit random byte in the effective balance filter. ```python def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: @@ -427,15 +435,18 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] Return from ``indices`` a random index sampled by effective balance. """ assert len(indices) > 0 - MAX_RANDOM_BYTE = 2**8 - 1 + MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra] i = uint64(0) total = uint64(len(indices)) while True: candidate_index = indices[compute_shuffled_index(i % total, total, seed)] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + # [Modified in Electra] + random_bytes = hash(seed + uint_to_bytes(i // 16)) + offset = i % 16 * 2 + random_value = bytes_to_uint64(random_bytes[offset:offset + 2]) effective_balance = state.validators[candidate_index].effective_balance # [Modified in Electra:EIP7251] - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_byte: + if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: return candidate_index i += 1 ``` @@ -577,7 +588,8 @@ def get_consolidation_churn_limit(state: BeaconState) -> Gwei: ```python def get_pending_balance_to_withdraw(state: BeaconState, validator_index: ValidatorIndex) -> Gwei: return sum( - withdrawal.amount for withdrawal in state.pending_partial_withdrawals if withdrawal.index == validator_index + withdrawal.amount for withdrawal in state.pending_partial_withdrawals + if withdrawal.validator_index == validator_index ) ``` @@ -608,7 +620,7 @@ def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[V #### Modified `get_next_sync_committee_indices` -*Note*: The function `get_next_sync_committee_indices` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA`. +*Note*: The function `get_next_sync_committee_indices` is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` and to use a 16-bit random value instead of an 8-bit random byte in the effective balance filter. ```python def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]: @@ -617,19 +629,22 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd """ epoch = Epoch(get_current_epoch(state) + 1) - MAX_RANDOM_BYTE = 2**8 - 1 + MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra] active_validator_indices = get_active_validator_indices(state, epoch) active_validator_count = uint64(len(active_validator_indices)) seed = get_seed(state, epoch, DOMAIN_SYNC_COMMITTEE) - i = 0 + i = uint64(0) sync_committee_indices: List[ValidatorIndex] = [] while len(sync_committee_indices) < SYNC_COMMITTEE_SIZE: shuffled_index = compute_shuffled_index(uint64(i % active_validator_count), active_validator_count, seed) candidate_index = active_validator_indices[shuffled_index] - random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32] + # [Modified in Electra] + random_bytes = hash(seed + uint_to_bytes(i // 16)) + offset = i % 16 * 2 + random_value = bytes_to_uint64(random_bytes[offset:offset + 2]) effective_balance = state.validators[candidate_index].effective_balance # [Modified in Electra:EIP7251] - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_byte: + if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: sync_committee_indices.append(candidate_index) i += 1 return sync_committee_indices @@ -958,8 +973,8 @@ def process_pending_consolidations(state: BeaconState) -> None: break # Calculate the consolidated balance - max_effective_balance = get_max_effective_balance(source_validator) - source_effective_balance = min(state.balances[pending_consolidation.source_index], max_effective_balance) + source_effective_balance = min( + state.balances[pending_consolidation.source_index], source_validator.effective_balance) # Move active balance to target. Excess balance is withdrawable. decrease_balance(state, pending_consolidation.source_index, source_effective_balance) @@ -1008,9 +1023,24 @@ class NewPayloadRequest(object): #### Engine APIs +##### Modified `is_valid_block_hash` + +*Note*: The function `is_valid_block_hash` is modified to include the additional `execution_requests_list`. + +```python +def is_valid_block_hash(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes]) -> bool: + """ + Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly. + """ + ... +``` + ##### Modified `notify_new_payload` -*Note*: The function `notify_new_payload` is modified to include the additional `execution_requests` parameter in Electra. +*Note*: The function `notify_new_payload` is modified to include the additional `execution_requests_list`. ```python def notify_new_payload(self: ExecutionEngine, @@ -1018,7 +1048,7 @@ def notify_new_payload(self: ExecutionEngine, parent_beacon_block_root: Root, execution_requests_list: Sequence[bytes]) -> bool: """ - Return ``True`` if and only if ``execution_payload`` and ``execution_requests`` + Return ``True`` if and only if ``execution_payload`` and ``execution_requests_list`` are valid with respect to ``self.execution_state``. """ ... @@ -1026,8 +1056,8 @@ def notify_new_payload(self: ExecutionEngine, ##### Modified `verify_and_notify_new_payload` -*Note*: The function `verify_and_notify_new_payload` is modified to pass the additional parameter `execution_requests` -when calling `notify_new_payload` in Electra. +*Note*: The function `verify_and_notify_new_payload` is modified to pass the additional parameter +`execution_requests_list` when calling `is_valid_block_hash` and `notify_new_payload` in Electra. ```python def verify_and_notify_new_payload(self: ExecutionEngine, @@ -1042,7 +1072,11 @@ def verify_and_notify_new_payload(self: ExecutionEngine, if b'' in execution_payload.transactions: return False - if not self.is_valid_block_hash(execution_payload, parent_beacon_block_root): + # [Modified in Electra] + if not self.is_valid_block_hash( + execution_payload, + parent_beacon_block_root, + execution_requests_list): return False if not self.is_valid_versioned_hashes(new_payload_request): @@ -1090,14 +1124,16 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], if withdrawal.withdrawable_epoch > epoch or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: break - validator = state.validators[withdrawal.index] + validator = state.validators[withdrawal.validator_index] has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE - has_excess_balance = state.balances[withdrawal.index] > MIN_ACTIVATION_BALANCE + has_excess_balance = state.balances[withdrawal.validator_index] > MIN_ACTIVATION_BALANCE if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance: - withdrawable_balance = min(state.balances[withdrawal.index] - MIN_ACTIVATION_BALANCE, withdrawal.amount) + withdrawable_balance = min( + state.balances[withdrawal.validator_index] - MIN_ACTIVATION_BALANCE, + withdrawal.amount) withdrawals.append(Withdrawal( index=withdrawal_index, - validator_index=withdrawal.index, + validator_index=withdrawal.validator_index, address=ExecutionAddress(validator.withdrawal_credentials[12:]), amount=withdrawable_balance, )) @@ -1205,7 +1241,7 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody, executi # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify commitments are under limit - assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA # [Modified in Electra:EIP7691] # Verify the execution payload is valid versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] assert execution_engine.verify_and_notify_new_payload( @@ -1531,7 +1567,7 @@ def process_withdrawal_request( exit_queue_epoch = compute_exit_epoch_and_update_churn(state, to_withdraw) withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) state.pending_partial_withdrawals.append(PendingPartialWithdrawal( - index=index, + validator_index=index, amount=to_withdraw, withdrawable_epoch=withdrawable_epoch, )) @@ -1643,8 +1679,8 @@ def process_consolidation_request( if not (has_correct_credential and is_correct_source_address): return - # Verify that target has execution withdrawal credentials - if not has_execution_withdrawal_credential(target_validator): + # Verify that target has compounding withdrawal credentials + if not has_compounding_withdrawal_credential(target_validator): return # Verify the source and the target are active @@ -1676,75 +1712,4 @@ def process_consolidation_request( source_index=source_index, target_index=target_index )) - - # Churn any target excess active balance of target and raise its max - if has_eth1_withdrawal_credential(target_validator): - switch_to_compounding_validator(state, target_index) -``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Electra testing only. -Modifications include: -1. Use `ELECTRA_FORK_VERSION` as the previous and current fork version. -2. Utilize the Electra `BeaconBlockBody` when constructing the initial `latest_block_header`. -3. *[New in Electra:EIP6110]* Add `deposit_requests_start_index` variable to the genesis state initialization. -4. *[New in Electra:EIP7251]* Initialize new fields to support increasing the maximum effective balance. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Hash32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit], - execution_payload_header: ExecutionPayloadHeader=ExecutionPayloadHeader() - ) -> BeaconState: - fork = Fork( - previous_version=ELECTRA_FORK_VERSION, # [Modified in Electra:EIP6110] for testing only - current_version=ELECTRA_FORK_VERSION, # [Modified in Electra:EIP6110] - epoch=GENESIS_EPOCH, - ) - state = BeaconState( - genesis_time=eth1_timestamp + GENESIS_DELAY, - fork=fork, - eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=uint64(len(deposits))), - latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), - randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy - deposit_requests_start_index=UNSET_DEPOSIT_REQUESTS_START_INDEX, # [New in Electra:EIP6110] - ) - - # Process deposits - leaves = list(map(lambda deposit: deposit.data, deposits)) - for index, deposit in enumerate(deposits): - deposit_data_list = List[DepositData, 2**DEPOSIT_CONTRACT_TREE_DEPTH](*leaves[:index + 1]) - state.eth1_data.deposit_root = hash_tree_root(deposit_data_list) - process_deposit(state, deposit) - - # Process deposit balance updates - validator_pubkeys = [v.pubkey for v in state.validators] - for deposit in state.pending_deposits: - validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) - increase_balance(state, validator_index, deposit.amount) - state.pending_deposits = [] - - # Process activations - for index, validator in enumerate(state.validators): - balance = state.balances[index] - # [Modified in Electra:EIP7251] - validator.effective_balance = min( - balance - balance % EFFECTIVE_BALANCE_INCREMENT, get_max_effective_balance(validator)) - if validator.effective_balance >= MIN_ACTIVATION_BALANCE: - validator.activation_eligibility_epoch = GENESIS_EPOCH - validator.activation_epoch = GENESIS_EPOCH - - # Set genesis validators root for domain separation and chain versioning - state.genesis_validators_root = hash_tree_root(state.validators) - - # Fill in sync committees - # Note: A duplicate committee is assigned for the current and next committee at genesis - state.current_sync_committee = get_next_sync_committee(state) - state.next_sync_committee = get_next_sync_committee(state) - - # Initialize the execution payload header - state.latest_execution_payload_header = execution_payload_header - - return state ``` diff --git a/specs/electra/p2p-interface.md b/specs/electra/p2p-interface.md index 0ea33df9f7..0016976e93 100644 --- a/specs/electra/p2p-interface.md +++ b/specs/electra/p2p-interface.md @@ -1,28 +1,46 @@ # Electra -- Networking -This document contains the consensus-layer networking specification for Electra. - -The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. - ## Table of contents +- [Introduction](#introduction) - [Modifications in Electra](#modifications-in-electra) + - [Configuration](#configuration) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) - - [Global topics](#global-topics) - - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof) - - [Attestation subnets](#attestation-subnets) - - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof) + - [Attestation subnets](#attestation-subnets) + - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BlobSidecarsByRoot v2](#blobsidecarsbyroot-v2) + - [BlobSidecarsByRange v2](#blobsidecarsbyrange-v2) +## Introduction + +This document contains the consensus-layer networking specification for Electra. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + ## Modifications in Electra +### Configuration + +*[New in Electra:EIP7691]* + +| Name | Value | Description | +|-------------------------------------|----------------------------------------------------------|-------------------------------------------------------------------| +| `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` | `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA` | Maximum number of blob sidecars in a single request | +| `BLOB_SIDECAR_SUBNET_COUNT_ELECTRA` | `9` | The number of blob sidecar subnets used in the gossipsub protocol | + ### The gossip domain: gossipsub Some gossip meshes are upgraded in the fork of Electra to support upgraded types. @@ -41,9 +59,16 @@ The specification around the creation, validation, and dissemination of messages The derivation of the `message-id` remains stable. -#### Global topics +##### Global topics + +###### `beacon_block` + +*Updated validation* -##### `beacon_aggregate_and_proof` +- _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- + i.e. validate that `len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA` + +###### `beacon_aggregate_and_proof` The following convenience variables are re-defined - `index = get_committee_indices(aggregate.committee_bits)[0]` @@ -52,9 +77,9 @@ The following validations are added: * [REJECT] `len(committee_indices) == 1`, where `committee_indices = get_committee_indices(aggregate)`. * [REJECT] `aggregate.data.index == 0` -#### Attestation subnets +##### Attestation subnets -##### `beacon_attestation_{subnet_id}` +###### `beacon_attestation_{subnet_id}` The topic is updated to propagate `SingleAttestation` objects. @@ -71,3 +96,78 @@ The following validations are removed: that is, it has exactly one participating validator (`len([bit for bit in aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). - _[REJECT]_ The number of aggregation bits matches the committee size -- i.e. `len(aggregation_bits) == len(get_beacon_committee(state, attestation.data.slot, index))`. + +### The Req/Resp domain + +#### Messages + +##### BlobSidecarsByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/2/` + +*[Modified in Electra:EIP7691]* + +The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|------------------------|-----------------------| +| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` | +| `ELECTRA_FORK_VERSION` | `electra.BlobSidecar` | + +Request Content: + +``` +( + List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS_ELECTRA] +) +``` + +Response Content: + +``` +( + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_ELECTRA] +) +``` + +*Updated validation* + +No more than `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` may be requested at a time. + +##### BlobSidecarsByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/2/` + +*[Modified in Electra:EIP7691]* + +The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: + +[1]: # (eth2spec: skip) + +| `fork_version` | Chunk SSZ type | +|------------------------|-----------------------| +| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` | +| `ELECTRA_FORK_VERSION` | `electra.BlobSidecar` | + +Request Content: + +``` +( + start_slot: Slot + count: uint64 +) +``` + +Response Content: + +``` +( + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_ELECTRA] +) +``` + +*Updated validation* + +Clients MUST respond with at least the blob sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` sidecars. diff --git a/specs/fulu/beacon-chain.md b/specs/fulu/beacon-chain.md new file mode 100644 index 0000000000..9412eb88c4 --- /dev/null +++ b/specs/fulu/beacon-chain.md @@ -0,0 +1,78 @@ +# Fulu -- The Beacon Chain + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) + - [Execution](#execution) + - [Execution payload](#execution-payload) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + + + + +## Introduction + +*Note:* This specification is built upon [Electra](../electra/beacon-chain.md) and is under active development. + +## Configuration + +### Execution + +| Name | Value | Description | +| - | - | - | +| `MAX_BLOBS_PER_BLOCK_FULU` | `uint64(12)` | *[New in Fulu:EIP7594]* Maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` | + +#### Execution payload + +##### Modified `process_execution_payload` + +```python +def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: + payload = body.execution_payload + + # Verify consistency of the parent hash with respect to the previous execution payload header + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify prev_randao + assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify commitments are under limit + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_FULU # [Modified in Fulu:EIP7594] + # Verify the execution payload is valid + versioned_hashes = [kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments] + assert execution_engine.verify_and_notify_new_payload( + NewPayloadRequest( + execution_payload=payload, + versioned_hashes=versioned_hashes, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests=body.execution_requests, + ) + ) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipts_root=payload.receipts_root, + logs_bloom=payload.logs_bloom, + prev_randao=payload.prev_randao, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + withdrawals_root=hash_tree_root(payload.withdrawals), + blob_gas_used=payload.blob_gas_used, + excess_blob_gas=payload.excess_blob_gas, + ) +``` diff --git a/specs/_features/eip7594/das-core.md b/specs/fulu/das-core.md similarity index 73% rename from specs/_features/eip7594/das-core.md rename to specs/fulu/das-core.md index 6a1d2758e0..25576bc1f4 100644 --- a/specs/_features/eip7594/das-core.md +++ b/specs/fulu/das-core.md @@ -1,4 +1,4 @@ -# EIP-7594 -- Data Availability Sampling Core +# Fulu -- Data Availability Sampling Core **Notice**: This document is a work-in-progress for researchers and implementers. @@ -13,27 +13,28 @@ - [Custom types](#custom-types) - [Configuration](#configuration) - [Data size](#data-size) - - [Networking](#networking) - [Custody setting](#custody-setting) - [Containers](#containers) - [`DataColumnSidecar`](#datacolumnsidecar) - [`MatrixEntry`](#matrixentry) - [Helper functions](#helper-functions) - - [`get_custody_columns`](#get_custody_columns) + - [`get_custody_groups`](#get_custody_groups) + - [`compute_columns_for_custody_group`](#compute_columns_for_custody_group) - [`compute_matrix`](#compute_matrix) - [`recover_matrix`](#recover_matrix) - [`get_data_column_sidecars`](#get_data_column_sidecars) - [Custody](#custody) - [Custody requirement](#custody-requirement) - [Public, deterministic selection](#public-deterministic-selection) -- [Subnet sampling](#subnet-sampling) +- [Custody sampling](#custody-sampling) - [Extended data](#extended-data) - [Column gossip](#column-gossip) - [Parameters](#parameters) - [Reconstruction and cross-seeding](#reconstruction-and-cross-seeding) - [FAQs](#faqs) - - [Row (blob) custody](#row-blob-custody) - - [Subnet stability](#subnet-stability) + - [Why don't nodes custody rows?](#why-dont-nodes-custody-rows) + - [Why don't we rotate custody over time?](#why-dont-we-rotate-custody-over-time) + - [Does having a lot of column subnets make the network unstable?](#does-having-a-lot-of-column-subnets-make-the-network-unstable) @@ -54,6 +55,7 @@ The following values are (non-configurable) constants used throughout the specif | - | - | - | | `RowIndex` | `uint64` | Row identifier in the matrix of cells | | `ColumnIndex` | `uint64` | Column identifier in the matrix of cells | +| `CustodyIndex` | `uint64` | Custody group identifier in the set of custody groups | ## Configuration @@ -63,18 +65,13 @@ The following values are (non-configurable) constants used throughout the specif | - | - | - | | `NUMBER_OF_COLUMNS` | `uint64(CELLS_PER_EXT_BLOB)` (= 128) | Number of columns in the extended data matrix | -### Networking - -| Name | Value | Description | -| - | - | - | -| `DATA_COLUMN_SIDECAR_SUBNET_COUNT` | `uint64(128)` | The number of data column sidecar subnets used in the gossipsub protocol | - ### Custody setting | Name | Value | Description | | - | - | - | | `SAMPLES_PER_SLOT` | `8` | Number of `DataColumnSidecar` random samples a node queries per slot | -| `CUSTODY_REQUIREMENT` | `4` | Minimum number of subnets an honest node custodies and serves samples from | +| `NUMBER_OF_CUSTODY_GROUPS` | `128` | Number of custody groups available for nodes to custody | +| `CUSTODY_REQUIREMENT` | `4` | Minimum number of custody groups an honest node custodies and serves samples from | ### Containers @@ -102,33 +99,39 @@ class MatrixEntry(Container): ## Helper functions -### `get_custody_columns` +### `get_custody_groups` ```python -def get_custody_columns(node_id: NodeID, custody_subnet_count: uint64) -> Sequence[ColumnIndex]: - assert custody_subnet_count <= DATA_COLUMN_SIDECAR_SUBNET_COUNT +def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence[CustodyIndex]: + assert custody_group_count <= NUMBER_OF_CUSTODY_GROUPS - subnet_ids: List[uint64] = [] + custody_groups: List[uint64] = [] current_id = uint256(node_id) - while len(subnet_ids) < custody_subnet_count: - subnet_id = ( + while len(custody_groups) < custody_group_count: + custody_group = CustodyIndex( bytes_to_uint64(hash(uint_to_bytes(uint256(current_id)))[0:8]) - % DATA_COLUMN_SIDECAR_SUBNET_COUNT + % NUMBER_OF_CUSTODY_GROUPS ) - if subnet_id not in subnet_ids: - subnet_ids.append(subnet_id) + if custody_group not in custody_groups: + custody_groups.append(custody_group) if current_id == UINT256_MAX: # Overflow prevention current_id = NodeID(0) current_id += 1 - assert len(subnet_ids) == len(set(subnet_ids)) + assert len(custody_groups) == len(set(custody_groups)) + return sorted(custody_groups) +``` - columns_per_subnet = NUMBER_OF_COLUMNS // DATA_COLUMN_SIDECAR_SUBNET_COUNT +### `compute_columns_for_custody_group` + +```python +def compute_columns_for_custody_group(custody_group: CustodyIndex) -> Sequence[ColumnIndex]: + assert custody_group < NUMBER_OF_CUSTODY_GROUPS + columns_per_group = NUMBER_OF_COLUMNS // NUMBER_OF_CUSTODY_GROUPS return sorted([ - ColumnIndex(DATA_COLUMN_SIDECAR_SUBNET_COUNT * i + subnet_id) - for i in range(columns_per_subnet) - for subnet_id in subnet_ids + ColumnIndex(NUMBER_OF_CUSTODY_GROUPS * i + custody_group) + for i in range(columns_per_group) ]) ``` @@ -220,21 +223,21 @@ def get_data_column_sidecars(signed_block: SignedBeaconBlock, ### Custody requirement -Each node downloads and custodies a minimum of `CUSTODY_REQUIREMENT` subnets per slot. The particular subnets that the node is required to custody are selected pseudo-randomly (more on this below). +Columns are grouped into custody groups. Nodes custodying a custody group MUST custody all the columns in that group. -A node *may* choose to custody and serve more than the minimum honesty requirement. Such a node explicitly advertises a number greater than `CUSTODY_REQUIREMENT` through the peer discovery mechanism, specifically by setting a higher value in the `custody_subnet_count` field within its ENR. This value can be increased up to `DATA_COLUMN_SIDECAR_SUBNET_COUNT`, indicating a super-full node. +A node *may* choose to custody and serve more than the minimum honesty requirement. Such a node explicitly advertises a number greater than `CUSTODY_REQUIREMENT` through the peer discovery mechanism, specifically by setting a higher value in the `custody_group_count` field within its ENR. This value can be increased up to `NUMBER_OF_CUSTODY_GROUPS`, indicating a super-full node. A node stores the custodied columns for the duration of the pruning period and responds to peer requests for samples on those columns. ### Public, deterministic selection -The particular columns that a node custodies are selected pseudo-randomly as a function (`get_custody_columns`) of the node-id and custody size -- importantly this function can be run by any party as the inputs are all public. +The particular columns/groups that a node custodies are selected pseudo-randomly as a function (`get_custody_groups`) of the node-id and custody size -- importantly this function can be run by any party as the inputs are all public. *Note*: increasing the `custody_size` parameter for a given `node_id` extends the returned list (rather than being an entirely new shuffle) such that if `custody_size` is unknown, the default `CUSTODY_REQUIREMENT` will be correct for a subset of the node's custody. -## Subnet sampling +## Custody sampling -At each slot, a node advertising `custody_subnet_count` downloads a minimum of `subnet_sampling_size = max(SAMPLES_PER_SLOT, custody_subnet_count)` total subnets. The corresponding set of columns is selected by `get_custody_columns(node_id, subnet_sampling_size)`, so that in particular the subset of columns to custody is consistent with the output of `get_custody_columns(node_id, custody_subnet_count)`. Sampling is considered successful if the node manages to retrieve all selected columns. +At each slot, a node advertising `custody_group_count` downloads a minimum of `sampling_size = max(SAMPLES_PER_SLOT, custody_group_count)` total custody groups. The corresponding set of columns is selected by `groups = get_custody_groups(node_id, sampling_size)` and `compute_columns_for_custody_group(group) for group in groups`, so that in particular the subset of columns to custody is consistent with the output of `get_custody_groups(node_id, custody_group_count)`. Sampling is considered successful if the node manages to retrieve all selected columns. ## Extended data @@ -246,7 +249,7 @@ In this construction, we extend the blobs using a one-dimensional erasure coding For each column -- use `data_column_sidecar_{subnet_id}` subnets, where `subnet_id` can be computed with the `compute_subnet_for_data_column_sidecar(column_index: ColumnIndex)` helper. The sidecars can be computed with `cells_and_kzg_proofs = [compute_cells_and_kzg_proofs(blob) for blob in blobs]` and then `get_data_column_sidecars(signed_block, cells_and_kzg_proofs)`. -Verifiable samples from their respective column are distributed on the assigned subnet. To custody a particular column, a node joins the respective gossipsub subnet. If a node fails to get a column on the column subnet, a node can also utilize the Req/Resp protocol to query the missing column from other peers. +Verifiable samples from their respective column are distributed on the assigned subnet. To custody columns in a particular custody group, a node joins the respective gossipsub subnets. If a node fails to get columns on the column subnets, a node can also utilize the Req/Resp protocol to query the missing columns from other peers. ## Reconstruction and cross-seeding @@ -262,7 +265,7 @@ Once the node obtains a column through reconstruction, the node MUST expose the ## FAQs -### Row (blob) custody +### Why don't nodes custody rows? In the one-dimension construction, a node samples the peers by requesting the whole `DataColumnSidecar`. In reconstruction, a node can reconstruct all the blobs by 50% of the columns. Note that nodes can still download the row via `blob_sidecar_{subnet_id}` subnets. @@ -273,6 +276,10 @@ The potential benefits of having row custody could include: However, for simplicity, we don't assign row custody assignments to nodes in the current design. -### Subnet stability +### Why don't we rotate custody over time? + +To start with a simple, stable backbone, for now, we don't shuffle the custody assignments via the deterministic custody selection helper `get_custody_groups`. However, staggered rotation likely needs to happen on the order of the pruning period to ensure subnets can be utilized for recovery. For example, introducing an `epoch` argument allows the function to maintain stability over many epochs. + +### Does having a lot of column subnets make the network unstable? -To start with a simple, stable backbone, for now, we don't shuffle the subnet assignments via the deterministic custody selection helper `get_custody_columns`. However, staggered rotation likely needs to happen on the order of the pruning period to ensure subnets can be utilized for recovery. For example, introducing an `epoch` argument allows the function to maintain stability over many epochs. +No, the number of subnets doesn't really matter. What matters to the network stability is the number of nodes and the churn rate in the network. If the number of the nodes is too low, it's likely to have a network partition when some nodes are down. For the churn rate, if the churn rate is high, we even need to have a higher number of nodes, since nodes are likely to be turned off more often. diff --git a/specs/_features/eip7594/fork-choice.md b/specs/fulu/fork-choice.md similarity index 94% rename from specs/_features/eip7594/fork-choice.md rename to specs/fulu/fork-choice.md index f406b7472a..4a20906804 100644 --- a/specs/_features/eip7594/fork-choice.md +++ b/specs/fulu/fork-choice.md @@ -1,4 +1,4 @@ -# EIP-7594 -- Fork Choice +# Fulu -- Fork Choice ## Table of contents @@ -16,7 +16,7 @@ ## Introduction -This is the modification of the fork choice accompanying EIP-7594. +This is the modification of the fork choice accompanying Fulu. ## Helpers @@ -25,10 +25,10 @@ This is the modification of the fork choice accompanying EIP-7594. ```python def is_data_available(beacon_block_root: Root) -> bool: # `retrieve_column_sidecars` is implementation and context dependent, replacing - # `retrieve_blobs_and_proofs`. For the given block root, it returns all column - # sidecars to sample, or raises an exception if they are not available. - # The p2p network does not guarantee sidecar retrieval outside of - # `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` epochs. + # `retrieve_blobs_and_proofs`. For the given block root, it returns all column + # sidecars to sample, or raises an exception if they are not available. + # The p2p network does not guarantee sidecar retrieval outside of + # `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` epochs. column_sidecars = retrieve_column_sidecars(beacon_block_root) return all( verify_data_column_sidecar(column_sidecar) @@ -67,7 +67,7 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: ) assert store.finalized_checkpoint.root == finalized_checkpoint_block - # [Modified in EIP7594] + # [Modified in Fulu:EIP7594] assert is_data_available(hash_tree_root(block)) # Check the block is valid and compute the post-state diff --git a/specs/_features/eip7594/fork.md b/specs/fulu/fork.md similarity index 82% rename from specs/_features/eip7594/fork.md rename to specs/fulu/fork.md index 4f2c17561f..e496467212 100644 --- a/specs/_features/eip7594/fork.md +++ b/specs/fulu/fork.md @@ -1,4 +1,4 @@ -# EIP7594 -- Fork Logic +# Fulu -- Fork Logic **Notice**: This document is a work-in-progress for researchers and implementers. @@ -12,7 +12,7 @@ - [Helper functions](#helper-functions) - [Misc](#misc) - [Modified `compute_fork_version`](#modified-compute_fork_version) -- [Fork to EIP7594](#fork-to-eip7594) +- [Fork to Fulu](#fork-to-fulu) - [Fork trigger](#fork-trigger) - [Upgrading the state](#upgrading-the-state) @@ -20,7 +20,7 @@ ## Introduction -This document describes the process of EIP7594 upgrade. +This document describes the process of Fulu upgrade. ## Configuration @@ -28,8 +28,8 @@ Warning: this configuration is not definitive. | Name | Value | | - | - | -| `EIP7594_FORK_VERSION` | `Version('0x06000000')` | -| `EIP7594_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | +| `FULU_FORK_VERSION` | `Version('0x06000000')` | +| `FULU_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | ## Helper functions @@ -42,8 +42,8 @@ def compute_fork_version(epoch: Epoch) -> Version: """ Return the fork version at the given ``epoch``. """ - if epoch >= EIP7594_FORK_EPOCH: - return EIP7594_FORK_VERSION + if epoch >= FULU_FORK_EPOCH: + return FULU_FORK_VERSION if epoch >= ELECTRA_FORK_EPOCH: return ELECTRA_FORK_VERSION if epoch >= DENEB_FORK_EPOCH: @@ -57,23 +57,22 @@ def compute_fork_version(epoch: Epoch) -> Version: return GENESIS_FORK_VERSION ``` -## Fork to EIP7594 +## Fork to Fulu ### Fork trigger -EIP7594 does not need a hard fork. We only add this fork doc for compiling this new feature in pyspec. +TBD. This fork is defined for testing purposes, the EIP may be combined with other consensus-layer upgrade. +For now, we assume the condition will be triggered at epoch `FULU_FORK_EPOCH`. -For now, we assume the condition will be triggered at epoch `EIP7594_FORK_EPOCH`. - -Note that for the pure EIP7594 networks, we don't apply `upgrade_to_eip7594` since it starts with EIP7594 version logic. +Note that for the pure Fulu networks, we don't apply `upgrade_to_fulu` since it starts with Fulu version logic. ### Upgrading the state -If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == EIP7594_FORK_EPOCH`, -an irregular state change is made to upgrade to EIP7594. +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == FULU_FORK_EPOCH`, +an irregular state change is made to upgrade to Fulu. ```python -def upgrade_to_eip7594(pre: electra.BeaconState) -> BeaconState: +def upgrade_to_fulu(pre: electra.BeaconState) -> BeaconState: epoch = electra.get_current_epoch(pre) post = BeaconState( # Versioning @@ -82,7 +81,7 @@ def upgrade_to_eip7594(pre: electra.BeaconState) -> BeaconState: slot=pre.slot, fork=Fork( previous_version=pre.fork.current_version, - current_version=EIP7594_FORK_VERSION, # [Modified in EIP7594] + current_version=FULU_FORK_VERSION, # [Modified in Fulu] epoch=epoch, ), # History diff --git a/specs/_features/eip7594/p2p-interface.md b/specs/fulu/p2p-interface.md similarity index 83% rename from specs/_features/eip7594/p2p-interface.md rename to specs/fulu/p2p-interface.md index de2d7e1f0b..abebbffecc 100644 --- a/specs/_features/eip7594/p2p-interface.md +++ b/specs/fulu/p2p-interface.md @@ -1,4 +1,4 @@ -# EIP-7594 -- Networking +# Fulu -- Networking **Notice**: This document is a work-in-progress for researchers and implementers. @@ -8,7 +8,8 @@ -- [Modifications in EIP-7594](#modifications-in-eip-7594) +- [Introduction](#introduction) +- [Modifications in Fulu](#modifications-in-fulu) - [Preset](#preset) - [Configuration](#configuration) - [Containers](#containers) @@ -28,19 +29,25 @@ - [`data_column_sidecar_{subnet_id}`](#data_column_sidecar_subnet_id) - [The Req/Resp domain](#the-reqresp-domain) - [Messages](#messages) - - [BlobSidecarsByRoot v2](#blobsidecarsbyroot-v2) - - [BlobSidecarsByRange v2](#blobsidecarsbyrange-v2) + - [BlobSidecarsByRoot v3](#blobsidecarsbyroot-v3) + - [BlobSidecarsByRange v3](#blobsidecarsbyrange-v3) - [DataColumnSidecarsByRoot v1](#datacolumnsidecarsbyroot-v1) - [DataColumnSidecarsByRange v1](#datacolumnsidecarsbyrange-v1) - [GetMetaData v3](#getmetadata-v3) - [The discovery domain: discv5](#the-discovery-domain-discv5) - [ENR structure](#enr-structure) - - [Custody subnet count](#custody-subnet-count) + - [Custody group count](#custody-group-count) -## Modifications in EIP-7594 +## Introduction + +This document contains the consensus-layer networking specification for Fulu. + +The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. + +## Modifications in Fulu ### Preset @@ -50,14 +57,14 @@ ### Configuration -*[New in EIP7594]* +*[New in Fulu:EIP7594]* -| Name | Value | Description | -|------------------------------------------------|----------------------------------------------------------|---------------------------------------------------------------------------| -| `MAX_REQUEST_DATA_COLUMN_SIDECARS` | `MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS` | Maximum number of data column sidecars in a single request | -| `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` | `2**12` (= 4096 epochs, ~18 days) | The minimum epoch range over which a node must serve data column sidecars | -| `MAX_REQUEST_BLOB_SIDECARS_EIP7594` | `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_EIP7594` | Maximum number of blob sidecars in a single request | -| `BLOB_SIDECAR_SUBNET_COUNT_EIP7594` | `2**3` (= 8) | The number of blob sidecar subnets used in the gossipsub protocol | +| Name | Value | Description | +|------------------------------------------------|-------------------------------------------------------|---------------------------------------------------------------------------| +| `DATA_COLUMN_SIDECAR_SUBNET_COUNT` | `128` | The number of data column sidecar subnets used in the gossipsub protocol | +| `MAX_REQUEST_DATA_COLUMN_SIDECARS` | `MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS` | Maximum number of data column sidecars in a single request | +| `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` | `2**12` (= 4096 epochs, ~18 days) | The minimum epoch range over which a node must serve data column sidecars | +| `MAX_REQUEST_BLOB_SIDECARS_FULU` | `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_FULU` | Maximum number of blob sidecars in a single request | ### Containers @@ -156,7 +163,7 @@ Where ### The gossip domain: gossipsub -Some gossip meshes are upgraded in the EIP-7594 fork to support upgraded types. +Some gossip meshes are upgraded in the Fulu fork to support upgraded types. #### Topics and messages @@ -167,7 +174,7 @@ Some gossip meshes are upgraded in the EIP-7594 fork to support upgraded types. *Updated validation* - _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- - i.e. validate that `len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_EIP7594` + i.e. validate that `len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_FULU` ##### Blob subnets @@ -204,25 +211,25 @@ The following validations MUST pass before forwarding the `sidecar: DataColumnSi #### Messages -##### BlobSidecarsByRoot v2 +##### BlobSidecarsByRoot v3 -**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/2/` +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/3/` -*[Updated in EIP7594]* +*[Modified in Fulu:EIP7594]* The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: [1]: # (eth2spec: skip) -| `fork_version` | Chunk SSZ type | -|------------------------|-----------------------| -| `EIP7594_FORK_VERSION` | `eip7594.BlobSidecar` | +| `fork_version` | Chunk SSZ type | +|---------------------|--------------------| +| `FULU_FORK_VERSION` | `fulu.BlobSidecar` | Request Content: ``` ( - List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS_EIP7594] + List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS_FULU] ) ``` @@ -230,27 +237,27 @@ Response Content: ``` ( - List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_EIP7594] + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_FULU] ) ``` *Updated validation* -No more than `MAX_REQUEST_BLOB_SIDECARS_EIP7594` may be requested at a time. +No more than `MAX_REQUEST_BLOB_SIDECARS_FULU` may be requested at a time. -##### BlobSidecarsByRange v2 +##### BlobSidecarsByRange v3 -**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/2/` +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/3/` -*[Updated in EIP7594]* +*[Modified in Fulu:EIP7594]* The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: [1]: # (eth2spec: skip) -| `fork_version` | Chunk SSZ type | -|------------------------|-----------------------| -| `EIP7594_FORK_VERSION` | `eip7594.BlobSidecar` | +| `fork_version` | Chunk SSZ type | +|---------------------|--------------------| +| `FULU_FORK_VERSION` | `fulu.BlobSidecar` | Request Content: @@ -265,27 +272,27 @@ Response Content: ``` ( - List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_EIP7594] + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_FULU] ) ``` *Updated validation* -Clients MUST respond with at least the blob sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOB_SIDECARS_EIP7594` sidecars. +Clients MUST respond with at least the blob sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOB_SIDECARS_FULU` sidecars. ##### DataColumnSidecarsByRoot v1 **Protocol ID:** `/eth2/beacon_chain/req/data_column_sidecars_by_root/1/` -*[New in EIP7594]* +*[New in Fulu:EIP7594]* The `` field is calculated as `context = compute_fork_digest(fork_version, genesis_validators_root)`: [1]: # (eth2spec: skip) -| `fork_version` | Chunk SSZ type | -|------------------------|-----------------------------| -| `EIP7594_FORK_VERSION` | `eip7594.DataColumnSidecar` | +| `fork_version` | Chunk SSZ type | +|---------------------|--------------------------| +| `FULU_FORK_VERSION` | `fulu.DataColumnSidecar` | Request Content: @@ -314,7 +321,7 @@ No more than `MAX_REQUEST_DATA_COLUMN_SIDECARS` may be requested at a time. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `DataColumnSidecar` payload. -Clients MUST support requesting sidecars since `minimum_request_epoch`, where `minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, EIP7594_FORK_EPOCH)`. If any root in the request content references a block earlier than `minimum_request_epoch`, peers MAY respond with error code `3: ResourceUnavailable` or not include the data column sidecar in the response. +Clients MUST support requesting sidecars since `minimum_request_epoch`, where `minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH)`. If any root in the request content references a block earlier than `minimum_request_epoch`, peers MAY respond with error code `3: ResourceUnavailable` or not include the data column sidecar in the response. Clients MUST respond with at least one sidecar, if they have it. Clients MAY limit the number of blocks and sidecars in the response. @@ -331,9 +338,9 @@ The `` field is calculated as `context = compute_fork_digest(fork [1]: # (eth2spec: skip) -| `fork_version` | Chunk SSZ type | -|------------------------|-----------------------------| -| `EIP7594_FORK_VERSION` | `eip7594.DataColumnSidecar` | +| `fork_version` | Chunk SSZ type | +|---------------------|--------------------------| +| `FULU_FORK_VERSION` | `fulu.DataColumnSidecar` | Request Content: @@ -364,7 +371,7 @@ The request MUST be encoded as an SSZ-container. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `DataColumnSidecar` payload. -Let `data_column_serve_range` be `[max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, EIP7594_FORK_EPOCH), current_epoch]`. +Let `data_column_serve_range` be `[max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH), current_epoch]`. Clients MUST keep a record of data column sidecars seen on the epoch range `data_column_serve_range` where `current_epoch` is defined by the current wall-clock time, and clients MUST support serving requests of data columns on this range. @@ -424,10 +431,10 @@ Requests the MetaData of a peer, using the new `MetaData` definition given above #### ENR structure -##### Custody subnet count +##### Custody group count -A new field is added to the ENR under the key `csc` to facilitate custody data column discovery. +A new field is added to the ENR under the key `cgc` to facilitate custody data column discovery. | Key | Value | |--------|------------------------------------------| -| `csc` | Custody subnet count, `uint64` big endian integer with no leading zero bytes (`0` is encoded as empty byte string) | +| `cgc` | Custody group count, `uint64` big endian integer with no leading zero bytes (`0` is encoded as empty byte string) | diff --git a/specs/_features/eip7594/peer-sampling.md b/specs/fulu/peer-sampling.md similarity index 91% rename from specs/_features/eip7594/peer-sampling.md rename to specs/fulu/peer-sampling.md index 5da03cb71a..d8b05bba13 100644 --- a/specs/_features/eip7594/peer-sampling.md +++ b/specs/fulu/peer-sampling.md @@ -1,4 +1,4 @@ -# EIP-7594 -- Peer Sampling +# Fulu -- Peer Sampling **Notice**: This document is a work-in-progress for researchers and implementers. @@ -23,7 +23,7 @@ ## Introduction -The purpose of this document is to complement [EIP-7594 -- Data Availability Sampling Core](das-core.md) by specifying the peer sampling functionality of the full PeerDAS protocol. Initially, this functionality may not be implemented by all clients. In such cases, it is replaced by [subnet sampling](das-core.md#subnet-sampling), which is an extension of the custody component of the protocol. +The purpose of this document is to complement [Fulu -- Data Availability Sampling Core](das-core.md) by specifying the peer sampling functionality of the full PeerDAS protocol. Initially, this functionality may not be implemented by all clients. In such cases, it is replaced by [subnet sampling](das-core.md#subnet-sampling), which is an extension of the custody component of the protocol. ## Helper functions @@ -97,7 +97,7 @@ For reference, the table below shows the number of samples and the number of all A node SHOULD maintain a diverse set of peers for each column and each slot by verifying responsiveness to sample queries. -A node SHOULD query for samples from selected peers via `DataColumnSidecarsByRoot` request. A node utilizes `get_custody_columns` helper to determine which peer(s) it could request from, identifying a list of candidate peers for each selected column. +A node SHOULD query for samples from selected peers via `DataColumnSidecarsByRoot` request. A node utilizes `get_custody_groups` helper to determine which peer(s) it could request from, identifying a list of candidate peers for each selected column. If more than one candidate peer is found for a given column, a node SHOULD randomize its peer selection to distribute sample query load in the network. Nodes MAY use peer scoring to tune this selection (for example, by using weighted selection or by using a cut-off threshold). If possible, it is also recommended to avoid requesting many columns from the same peer in order to avoid relying on and exposing the sample selection to a single peer. @@ -115,4 +115,4 @@ A DAS provider is a consistently-available-for-DAS-queries, super-full (or high DAS providers can also be found out-of-band and configured into a node to connect to directly and prioritize. Nodes can add some set of these to their local configuration for persistent connection to bolster their DAS quality of service. -Such direct peering utilizes a feature supported out of the box today on all nodes and can complement (and reduce attackability and increase quality-of-service) alternative peer discovery mechanisms. \ No newline at end of file +Such direct peering utilizes a feature supported out of the box today on all nodes and can complement (and reduce attackability and increase quality-of-service) alternative peer discovery mechanisms. diff --git a/specs/_features/eip7594/polynomial-commitments-sampling.md b/specs/fulu/polynomial-commitments-sampling.md similarity index 98% rename from specs/_features/eip7594/polynomial-commitments-sampling.md rename to specs/fulu/polynomial-commitments-sampling.md index 36f208bbf4..c85e870896 100644 --- a/specs/_features/eip7594/polynomial-commitments-sampling.md +++ b/specs/fulu/polynomial-commitments-sampling.md @@ -1,4 +1,4 @@ -# EIP-7594 -- Polynomial Commitments Sampling +# Fulu -- Polynomial Commitments Sampling ## Table of contents @@ -24,7 +24,6 @@ - [Polynomials in coefficient form](#polynomials-in-coefficient-form) - [`polynomial_eval_to_coeff`](#polynomial_eval_to_coeff) - [`add_polynomialcoeff`](#add_polynomialcoeff) - - [`neg_polynomialcoeff`](#neg_polynomialcoeff) - [`multiply_polynomialcoeff`](#multiply_polynomialcoeff) - [`divide_polynomialcoeff`](#divide_polynomialcoeff) - [`interpolate_polynomialcoeff`](#interpolate_polynomialcoeff) @@ -252,16 +251,6 @@ def add_polynomialcoeff(a: PolynomialCoeff, b: PolynomialCoeff) -> PolynomialCoe return PolynomialCoeff([a[i] + (b[i] if i < length_b else BLSFieldElement(0)) for i in range(length_a)]) ``` -#### `neg_polynomialcoeff` - -```python -def neg_polynomialcoeff(a: PolynomialCoeff) -> PolynomialCoeff: - """ - Negative of coefficient form polynomial ``a``. - """ - return PolynomialCoeff([-x for x in a]) -``` - #### `multiply_polynomialcoeff` ```python diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 0e1953946e..396e4671b8 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1,19 +1,11 @@ # Phase 0 -- Networking -This document contains the networking specification for Phase 0. - -It consists of four main sections: - -1. A specification of the network fundamentals. -2. A specification of the three network interaction *domains* of the proof-of-stake consensus layer: (a) the gossip domain, (b) the discovery domain, and (c) the Req/Resp domain. -3. The rationale and further explanation for the design choices made in the previous two sections. -4. An analysis of the maturity/state of the libp2p features required by this spec across the languages in which clients are being developed. - ## Table of contents +- [Introduction](#introduction) - [Network fundamentals](#network-fundamentals) - [Transport](#transport) - [Encryption and identification](#encryption-and-identification) @@ -115,6 +107,17 @@ It consists of four main sections: +## Introduction + +This document contains the networking specification for Phase 0. + +It consists of four main sections: + +1. A specification of the network fundamentals. +2. A specification of the three network interaction *domains* of the proof-of-stake consensus layer: (a) the gossip domain, (b) the discovery domain, and (c) the Req/Resp domain. +3. The rationale and further explanation for the design choices made in the previous two sections. +4. An analysis of the maturity/state of the libp2p features required by this spec across the languages in which clients are being developed. + ## Network fundamentals This section outlines the specification for the networking stack in Ethereum consensus-layer clients. @@ -960,7 +963,8 @@ The Ethereum Node Record (ENR) for an Ethereum consensus client MUST contain the The ENR MAY contain the following entries: - An IPv4 address (`ip` field) and/or IPv6 address (`ip6` field). -- A TCP port (`tcp` field) representing the local libp2p listening port. +- A TCP port (`tcp` field) representing the local libp2p TCP listening port. +- A QUIC port (`quic` field) representing the local libp2p QUIC (UDP) listening port. - A UDP port (`udp` field) representing the local discv5 listening port. Specifications of these parameters can be found in the [ENR Specification](http://eips.ethereum.org/EIPS/eip-778). diff --git a/specs/phase0/weak-subjectivity.md b/specs/phase0/weak-subjectivity.md index 52953c34df..00ab559b32 100644 --- a/specs/phase0/weak-subjectivity.md +++ b/specs/phase0/weak-subjectivity.md @@ -86,9 +86,9 @@ A detailed analysis of the calculation of the weak subjectivity period is made i ```python def compute_weak_subjectivity_period(state: BeaconState) -> uint64: """ - Returns the weak subjectivity period for the current ``state``. + Returns the weak subjectivity period for the current ``state``. This computation takes into account the effect of: - - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and + - validator set churn (bounded by ``get_validator_churn_limit()`` per epoch), and - validator balance top-ups (bounded by ``MAX_DEPOSITS * SLOTS_PER_EPOCH`` per epoch). A detailed calculation can be found at: https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf @@ -113,7 +113,7 @@ def compute_weak_subjectivity_period(state: BeaconState) -> uint64: ws_period += ( 3 * N * D * t // (200 * Delta * (T - t)) ) - + return ws_period ``` diff --git a/ssz/merkle-proofs.md b/ssz/merkle-proofs.md index ffc543f95e..36d0b83ac1 100644 --- a/ssz/merkle-proofs.md +++ b/ssz/merkle-proofs.md @@ -71,7 +71,7 @@ Note that the generalized index has the convenient property that the two childre ```python def merkle_tree(leaves: Sequence[Bytes32]) -> Sequence[Bytes32]: """ - Return an array representing the tree nodes by generalized index: + Return an array representing the tree nodes by generalized index: [0, 1, 2, 3, 4, 5, 6, 7], where each layer is a power of 2. The 0 index is ignored. The 1 index is the root. The result will be twice the size as the padded bottom layer for the input leaves. """ diff --git a/sync/optimistic.md b/sync/optimistic.md index 1fd1ba46e6..fd83f9a691 100644 --- a/sync/optimistic.md +++ b/sync/optimistic.md @@ -326,7 +326,7 @@ optimistic blocks (and vice-versa). ### Why sync optimistically? -Most execution engines use state sync as a default sync mechanism on Ethereum Mainnet +Most execution engines use state sync as a default sync mechanism on Ethereum Mainnet because executing blocks from genesis takes several weeks on commodity hardware. State sync requires the knowledge of the current head of the chain to converge eventually. diff --git a/tests/README.md b/tests/README.md index 8c281155c5..798627577d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,55 +15,36 @@ Use an OS that has Python 3.8 or above. For example, Debian 11 (bullseye) git clone https://github.com/ethereum/consensus-specs.git cd consensus-specs ``` -3. Create the specifications and tests: +3. Create the specifications and tests: ```sh - make install_test - make pyspec + make ``` To read more about creating the environment, [see here](core/pyspec/README.md). ### Running your first test +Use `make` to run the `test_empty_block_transition` tests against the Altair fork like so: -1. Enter the virtual Python environment: - ```sh - cd ~/consensus-specs - . venv/bin/activate - ``` -2. Run a sanity check test against Altair fork: - ```sh - cd tests/core/pyspec - python -m pytest -k test_empty_block_transition --fork altair eth2spec - ``` -3. The output should be similar to: - ``` - ============================= test session starts ============================== - platform linux -- Python 3.9.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 - rootdir: /home/qbzzt1/consensus-specs - plugins: cov-2.12.1, forked-1.3.0, xdist-2.3.0 - collected 629 items / 626 deselected / 3 selected - - eth2spec/test/bellatrix/sanity/test_blocks.py . [ 33%] - eth2spec/test/phase0/sanity/test_blocks.py .. [100%] - - =============================== warnings summary =============================== - ../../../venv/lib/python3.9/site-packages/cytoolz/compatibility.py:2 - /home/qbzzt1/consensus-specs/venv/lib/python3.9/site-packages/cytoolz/compatibility.py:2: - DeprecationWarning: The toolz.compatibility module is no longer needed in Python 3 and has - been deprecated. Please import these utilities directly from the standard library. This - module will be removed in a future release. - warnings.warn("The toolz.compatibility module is no longer " - - -- Docs: https://docs.pytest.org/en/stable/warnings.html - ================ 3 passed, 626 deselected, 1 warning in 16.81s ================= - ``` - +``` +$ make test k=test_empty_block_transition fork=altair +Building all pyspecs +... +================================= test session starts ================================== +platform darwin -- Python 3.10.3, pytest-8.3.3, pluggy-1.5.0 +rootdir: /Users/jtraglia/Projects/jtraglia/consensus-specs +plugins: cov-5.0.0, xdist-3.6.1 +20 workers [3 items] +s.. [100%] +=================================== warnings summary =================================== +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +====================== 2 passed, 1 skipped, 42 warnings in 7.97s ======================= +``` ## The "Hello, World" of Consensus Spec Tests One of the `test_empty_block_transition` tests is implemented by a function with the same -name located in +name located in [`~/consensus-specs/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py). To learn how consensus spec tests are written, let's go over the code: @@ -94,10 +75,10 @@ This type of test receives two parameters: ```python pre_slot = state.slot -``` +``` A slot is a unit of time (every 12 seconds in mainnet), for which a specific validator (selected randomly but in a -deterministic manner) is a proposer. The proposer can propose a block during that slot. +deterministic manner) is a proposer. The proposer can propose a block during that slot. ```python pre_eth1_votes = len(state.eth1_data_votes) @@ -151,7 +132,7 @@ More `yield` statements. The output of a consensus test is: # Check that the new parent root is correct assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root - + # Random data changed assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != pre_mix ``` @@ -160,16 +141,16 @@ Finally we assertions that test the transition was legitimate. In this case we h 1. One item was added to `eth1_data_votes` 2. The new block's `parent_root` is the same as the block in the previous location -3. The random data that every block includes was changed. +3. The random data that every block includes was changed. ## New Tests The easiest way to write a new test is to copy and modify an existing one. For example, -lets write a test where the first slot of the beacon chain is empty (because the assigned +lets write a test where the first slot of the beacon chain is empty (because the assigned proposer is offline, for example), and then there's an empty block in the second slot. -We already know how to accomplish most of what we need for this test, but the only way we know +We already know how to accomplish most of what we need for this test, but the only way we know to advance the state is `state_transition_and_sign_block`, a function that also puts a block into the slot. So let's see if the function's definition tells us how to advance the state without a block. @@ -180,7 +161,7 @@ First, we need to find out where the function is located. Run: find . -name '*.py' -exec grep 'def state_transition_and_sign_block' {} \; -print ``` -And you'll find that the function is defined in +And you'll find that the function is defined in [`eth2spec/test/helpers/state.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/state.py). Looking in that file, we see that the second function is: @@ -209,7 +190,7 @@ This looks like exactly what we need. So we add this call before we create the e . ``` -That's it. Our new test works (copy `test_empty_block_transition`, rename it, add the `next_slot` call, and then run it to +That's it. Our new test works (copy `test_empty_block_transition`, rename it, add the `next_slot` call, and then run it to verify this). @@ -218,7 +199,7 @@ verify this). It is important to make sure that the system rejects invalid input, so our next step is to deal with cases where the protocol is supposed to reject something. To see such a test, look at `test_prev_slot_block_transition` (in the same -file we used previously, +file we used previously, [`~/consensus-specs/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py)). ```python @@ -249,7 +230,7 @@ Transition to the new slot, which naturally has a different proposer. ``` Specify that the function `transition_unsigned_block` will cause an assertion error. -You can see this function in +You can see this function in [`~/consensus-specs/tests/core/pyspec/eth2spec/test/helpers/block.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/block.py), and one of the tests is that the block must be for this slot: > ```python @@ -265,14 +246,14 @@ be called later. ``` Set the block's state root to the current state hash tree root, which identifies this block as -belonging to this slot (even though it was created for the previous slot). +belonging to this slot (even though it was created for the previous slot). -```python +```python signed_block = sign_block(spec, state, block, proposer_index=proposer_index) ``` Notice that `proposer_index` is the variable we set earlier, *before* we advanced -the slot with `spec.process_slots(state, state.slot + 1)`. It is not the proposer +the slot with `spec.process_slots(state, state.slot + 1)`. It is not the proposer for the current state. ```python @@ -296,8 +277,8 @@ includes the block hash of the proposed new head of the execution layer. For every slot there is also a randomly selected committee of validators that needs to vote whether the new consensus layer block is valid, which requires the proposed head of the execution chain to -also be a valid block. These votes are called [attestations](https://notes.ethereum.org/@hww/aggregation#112-Attestation), -and they are sent as independent messages. The proposer for a block is able to include attestations from previous slots, +also be a valid block. These votes are called [attestations](https://notes.ethereum.org/@hww/aggregation#112-Attestation), +and they are sent as independent messages. The proposer for a block is able to include attestations from previous slots, which is how they get on chain to form consensus, reward honest validators, etc. [You can see a simple successful attestation test here](https://github.com/ethereum/consensus-specs/blob/926e5a3d722df973b9a12f12c015783de35cafa9/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py#L26-L30): @@ -326,8 +307,8 @@ To see an attestion "from the inside" we need to follow it. > ``` > > Only two parameters, `spec` and `state` are required. However, there are four other parameters that can affect -> the attestation created by this function. -> +> the attestation created by this function. +> > > ```python > # If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed. @@ -345,10 +326,10 @@ To see an attestion "from the inside" we need to follow it. > attestation_data = build_attestation_data( > spec, state, slot=slot, index=index > ) -> ``` +> ``` > -> Build the actual attestation. You can see this function -> [here](https://github.com/ethereum/consensus-specs/blob/30fe7ba1107d976100eb0c3252ca7637b791e43a/tests/core/pyspec/eth2spec/test/helpers/attestations.py#L53-L85) +> Build the actual attestation. You can see this function +> [here](https://github.com/ethereum/consensus-specs/blob/30fe7ba1107d976100eb0c3252ca7637b791e43a/tests/core/pyspec/eth2spec/test/helpers/attestations.py#L53-L85) > to see the exact data in an attestation. > > ```python @@ -358,17 +339,17 @@ To see an attestion "from the inside" we need to follow it. > attestation_data.index, > ) > ``` -> +> > This is the committee that is supposed to approve or reject the proposed block. -> -> ```python -> +> +> ```python +> > committee_size = len(beacon_committee) > aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size)) > ``` -> +> > There's a bit for every committee member to see if it approves or not. -> +> > ```python > attestation = spec.Attestation( > aggregation_bits=aggregation_bits, @@ -376,15 +357,15 @@ To see an attestion "from the inside" we need to follow it. > ) > # fill the attestation with (optionally filtered) participants, and optionally sign it > fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set) -> -> return attestation +> +> return attestation > ``` ```python next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) ``` -Attestations have to appear after the block they attest for, so we advance +Attestations have to appear after the block they attest for, so we advance `spec.MIN_ATTESTATION_INCLUSION_DELAY` slots before creating the block that includes the attestation. Currently a single block is sufficient, but that may change in the future. @@ -392,7 +373,7 @@ Currently a single block is sufficient, but that may change in the future. yield from run_attestation_processing(spec, state, attestation) ``` -[This function](https://github.com/ethereum/consensus-specs/blob/30fe7ba1107d976100eb0c3252ca7637b791e43a/tests/core/pyspec/eth2spec/test/helpers/attestations.py#L13-L50) +[This function](https://github.com/ethereum/consensus-specs/blob/30fe7ba1107d976100eb0c3252ca7637b791e43a/tests/core/pyspec/eth2spec/test/helpers/attestations.py#L13-L50) processes the attestation and returns the result. @@ -419,15 +400,15 @@ In the last line you can see two conditions being asserted: arrive too early. 2. `state.slot <= data.slot + SLOTS_PER_EPOCH` which verifies that the attestation doesn't arrive too late. - + This is how the consensus layer tests deal with edge cases, by asserting the conditions required for the -values to be legitimate. In the case of these particular conditions, they are tested +values to be legitimate. In the case of these particular conditions, they are tested [here](https://github.com/ethereum/consensus-specs/blob/926e5a3d722df973b9a12f12c015783de35cafa9/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py#L87-L104). One test checks what happens if the attestation is too early, and another if it is too late. However, it is not enough to ensure we reject invalid blocks. It is also necessary to ensure we accept all valid blocks. You saw earlier -a test (`test_success`) that tested that being `MIN_ATTESTATION_INCLUSION_DELAY` after the data for which we attest is enough. -Now we'll write a similar test that verifies that being `SLOTS_PER_EPOCH` away is still valid. To do this, we modify the +a test (`test_success`) that tested that being `MIN_ATTESTATION_INCLUSION_DELAY` after the data for which we attest is enough. +Now we'll write a similar test that verifies that being `SLOTS_PER_EPOCH` away is still valid. To do this, we modify the `test_after_epoch_slots` function. We need two changes: 1. Call `transition_to_slot_via_block` with one less slot to advance @@ -445,16 +426,13 @@ def test_almost_after_epoch_slots(spec, state): transition_to_slot_via_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) yield from run_attestation_processing(spec, state, attestation) -``` +``` Add this function to the file `consensus-specs/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py`, and run the test against Altair fork: ```sh -cd ~/consensus-specs -. venv/bin/activate -cd tests/core/pyspec -python -m pytest -k almost_after --fork altair eth2spec +make test k=almost_after fork=altair ``` You should see it ran successfully (although you might get a warning, you can ignore it) @@ -463,7 +441,7 @@ You should see it ran successfully (although you might get a warning, you can ig So far we've ran tests against the formal specifications. This is a way to check the specifications are what we expect, but it doesn't actually check the beacon chain clients. The way these tests get applied -by clients is that every few weeks +by clients is that every few weeks [new test specifications are released](https://github.com/ethereum/consensus-spec-tests/releases), in a format [documented here](https://github.com/ethereum/consensus-specs/tree/dev/tests/formats). All the consensus layer clients implement test-runners that consume the test vectors in this standard format. diff --git a/tests/core/pyspec/README.md b/tests/core/pyspec/README.md index baa1322771..2fdd107fb1 100644 --- a/tests/core/pyspec/README.md +++ b/tests/core/pyspec/README.md @@ -7,29 +7,6 @@ With this executable spec, test-generators can easily create test-vectors for client implementations, and the spec itself can be verified to be consistent and coherent through sanity tests implemented with pytest. -## Dev Install - -First, create a `venv` and install the developer dependencies (`test` and `lint` extras): - -```shell -make install_test -``` - -All the dynamic parts of the spec are built with: - -```shell -(venv) python setup.py pyspecdev -``` - -Unlike the regular install, this outputs spec files to their intended source location, -to enable debuggers to navigate between packages and generated code, without fragile directory linking. - -By default, when installing the `eth2spec` as package in non-develop mode, -the distutils implementation of the `setup` runs `build`, which is extended to run the same `pyspec` work, -but outputs into the standard `./build/lib` output. -This enables the `consensus-specs` repository to be installed like any other python package. - - ## Py-tests These tests are not intended for client-consumption. @@ -38,61 +15,41 @@ However, most of the tests can be run in generator-mode, to output test vectors ### How to run tests -#### Automated - -Run `make test` from the root of the specs repository (after running `make install_test` if have not before). - -Note that the `make` commands run through the build steps: it runs the `build` output, not the local package source files. - -#### Manual - -See `Dev install` for test pre-requisites. - -Tests are built for `pytest`. - -Caveats: -- Working directory must be `./tests/core/pyspec`. The work-directory is important to locate eth2 configuration files. -- Run `pytest` as module. It avoids environment differences, and the behavior is different too: - `pytest` as module adds the current directory to the `sys.path` +To run all tests: +```shell +make test +``` -Full test usage, with explicit configuration for illustration of options usage: +To run all tests under the minimal preset: ```shell -(venv) python -m pytest --preset=minimal eth2spec +make test preset=minimal ``` -Or, to run a specific test file, specify the full path: +Or, to run a specific test function specify `k=`: ```shell -(venv) python -m pytest --preset=minimal ./eth2spec/test/phase0/block_processing/test_process_attestation.py +make test k=test_verify_kzg_proof ``` -Or, to run a specific test function (specify the `eth2spec` module, or the script path if the keyword is ambiguous): +Or, to run a specific test function under a single fork specify `k=`: ```shell -(venv) python -m pytest --preset=minimal -k test_success_multi_proposer_index_iterations eth2spec +make test fork=phase0 ``` -Options: -- `--preset`, to change the preset (compile-time configurables). Defaults to `minimal`, can be set to `mainnet`. - Use `@spec_configured_state_test({config here...}` to override runtime configurables on a per-test basis. -- `--disable-bls`, to disable BLS (only for tests that can run without) -- `--bls-type`, `milagro` or `py_ecc` (default) +Note: these options can be used together, like: -### How to view code coverage report - -Run `make open_cov` from the root of the specs repository after running `make test` to open the html code coverage report. +```shell +make test preset=minimal k=test_verify_kzg_proof fork=deneb +``` -### Advanced +### How to view code coverage report -Building spec files from any markdown sources, to a custom location: -```bash -(venv) python setup.py pyspec --spec-fork=phase0 --md-doc-paths="specs/phase0/beacon-chain.md specs/phase0/fork-choice.md" --out-dir=my_spec_dir -``` +Run `make coverage` to run all tests and open the html code coverage report. ## Contributing Contributions are welcome, but consider implementing your idea as part of the spec itself first. The pyspec is not a replacement. - ## License Same as the spec itself; see [LICENSE](../../../LICENSE) file in the specs repository root. diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index e3af99e3c0..e7fd637b5a 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.5.0-alpha.9 +1.5.0-alpha.10 diff --git a/tests/core/pyspec/eth2spec/config/README.md b/tests/core/pyspec/eth2spec/config/README.md index c03d890c20..a61461f01a 100644 --- a/tests/core/pyspec/eth2spec/config/README.md +++ b/tests/core/pyspec/eth2spec/config/README.md @@ -19,5 +19,5 @@ spec.config = spec.Configuration(**config_util.load_config_file(Path('mytestnet. ``` Note: previously the testnet config files included both preset and runtime-configuration data. -The new config loader is compatible with this: all config vars are loaded from the file, -but those that have become presets can be ignored. +The new config loader is compatible with this: all config vars are loaded from the file, +but those that have become presets can be ignored. diff --git a/tests/core/pyspec/eth2spec/gen_helpers/README.md b/tests/core/pyspec/eth2spec/gen_helpers/README.md index bf791ccfea..8fda6b585e 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/README.md +++ b/tests/core/pyspec/eth2spec/gen_helpers/README.md @@ -17,10 +17,10 @@ Options: If true, all cases will run regardless, and files will be overwritten. Other existing files are not deleted. --c CONFIGS_PATH -- The directory to load configs for pyspec from. A config is a simple key-value yaml file. +-c CONFIGS_PATH -- The directory to load configs for pyspec from. A config is a simple key-value yaml file. Use `../../configs/` when running from the root dir of a generator, and requiring the standard spec configs. -[-l [CONFIG_LIST [CONFIG_LIST ...]]] -- Optional. Define which configs to run. +[-l [CONFIG_LIST [CONFIG_LIST ...]]] -- Optional. Define which configs to run. Test providers loading other configs will be ignored. If none are specified, no config will be ignored. ``` @@ -45,10 +45,10 @@ The yielding pattern is: Test part output kinds: - `ssz`: value is expected to be a `bytes`, and the raw data is written to a `.ssz_snappy` file. - `data`: value is expected to be any Python object that can be dumped as YAML. Output is written to `.yaml` -- `meta`: these key-value pairs are collected into a dict, and then collectively written to a metadata +- `meta`: these key-value pairs are collected into a dict, and then collectively written to a metadata file named `meta.yaml`, if anything is yielded with `meta` empty. The `vector_test()` decorator can detect pyspec SSZ types, and output them both as `data` and `ssz`, for the test consumer to choose. -Note that the yielded outputs are processed before the test continues. It is safe to yield information that later mutates, +Note that the yielded outputs are processed before the test continues. It is safe to yield information that later mutates, as the output will already be encoded to yaml or ssz bytes. This avoids the need to deep-copy the whole object. diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/bellatrix/genesis/test_initialization.py deleted file mode 100644 index 140d8708ca..0000000000 --- a/tests/core/pyspec/eth2spec/test/bellatrix/genesis/test_initialization.py +++ /dev/null @@ -1,122 +0,0 @@ -from eth2spec.test.context import ( - BELLATRIX, - single_phase, - spec_test, - with_presets, - with_phases, - with_bellatrix_and_later, -) -from eth2spec.test.helpers.constants import MINIMAL -from eth2spec.test.helpers.deposits import ( - prepare_full_genesis_deposits, -) -from eth2spec.test.helpers.genesis import ( - get_sample_genesis_execution_payload_header, -) - - -def eth1_init_data(eth1_block_hash, eth1_timestamp): - yield 'eth1', { - 'eth1_block_hash': '0x' + eth1_block_hash.hex(), - 'eth1_timestamp': int(eth1_timestamp), - } - - -@with_phases([BELLATRIX]) -@spec_test -@single_phase -@with_presets([MINIMAL], reason="too slow") -def test_initialize_pre_transition_no_param(spec): - deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - deposits, deposit_root, _ = prepare_full_genesis_deposits( - spec, - spec.MAX_EFFECTIVE_BALANCE, - deposit_count, - signed=True, - ) - - eth1_block_hash = b'\x12' * 32 - eth1_timestamp = spec.config.MIN_GENESIS_TIME - - yield from eth1_init_data(eth1_block_hash, eth1_timestamp) - yield 'deposits', deposits - - # initialize beacon_state *without* an execution_payload_header - yield 'execution_payload_header', 'meta', False - state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) - - assert not spec.is_merge_transition_complete(state) - - yield 'state', state - - -@with_bellatrix_and_later -@spec_test -@single_phase -@with_presets([MINIMAL], reason="too slow") -def test_initialize_pre_transition_empty_payload(spec): - deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - deposits, deposit_root, _ = prepare_full_genesis_deposits( - spec, - spec.MAX_EFFECTIVE_BALANCE, - deposit_count, - signed=True, - ) - - eth1_block_hash = b'\x12' * 32 - eth1_timestamp = spec.config.MIN_GENESIS_TIME - - yield from eth1_init_data(eth1_block_hash, eth1_timestamp) - yield 'deposits', deposits - - # initialize beacon_state *with* an *empty* execution_payload_header - yield 'execution_payload_header', 'meta', True - execution_payload_header = spec.ExecutionPayloadHeader() - state = spec.initialize_beacon_state_from_eth1( - eth1_block_hash, - eth1_timestamp, - deposits, - execution_payload_header=execution_payload_header, - ) - - assert not spec.is_merge_transition_complete(state) - - yield 'execution_payload_header', execution_payload_header - - yield 'state', state - - -@with_bellatrix_and_later -@spec_test -@single_phase -@with_presets([MINIMAL], reason="too slow") -def test_initialize_post_transition(spec): - deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - deposits, deposit_root, _ = prepare_full_genesis_deposits( - spec, - spec.MAX_EFFECTIVE_BALANCE, - deposit_count, - signed=True, - ) - - eth1_block_hash = b'\x12' * 32 - eth1_timestamp = spec.config.MIN_GENESIS_TIME - - yield from eth1_init_data(eth1_block_hash, eth1_timestamp) - yield 'deposits', deposits - - # initialize beacon_state *with* an execution_payload_header - yield 'execution_payload_header', 'meta', True - genesis_execution_payload_header = get_sample_genesis_execution_payload_header(spec) - state = spec.initialize_beacon_state_from_eth1( - eth1_block_hash, - eth1_timestamp, - deposits, - execution_payload_header=genesis_execution_payload_header, - ) - - yield 'execution_payload_header', genesis_execution_payload_header - - assert spec.is_merge_transition_complete(state) - - yield 'state', state diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 8b2e8de6d3..8c960cfc75 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -8,7 +8,7 @@ from .exceptions import SkippedTest from .helpers.constants import ( PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, - EIP7594, + FULU, WHISK, MINIMAL, ALL_PHASES, @@ -558,7 +558,7 @@ def wrapper(*args, spec: Spec, **kw): with_deneb_and_later = with_all_phases_from(DENEB) with_electra_and_later = with_all_phases_from(ELECTRA) with_whisk_and_later = with_all_phases_from(WHISK, all_phases=ALLOWED_TEST_RUNNER_FORKS) -with_eip7594_and_later = with_all_phases_from(EIP7594, all_phases=ALLOWED_TEST_RUNNER_FORKS) +with_fulu_and_later = with_all_phases_from(FULU, all_phases=ALLOWED_TEST_RUNNER_FORKS) class quoted_str(str): diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py index 905cb390eb..2845af4634 100644 --- a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py @@ -7,7 +7,7 @@ from eth2spec.test.helpers.constants import ( DENEB, - EIP7594, + FULU, ) from eth2spec.test.helpers.block import ( @@ -39,7 +39,7 @@ def get_block_with_blob(spec, state, rng=None): return block, blobs, blob_kzg_proofs -@with_all_phases_from_except(DENEB, [EIP7594]) +@with_all_phases_from_except(DENEB, [FULU]) @spec_state_test def test_simple_blob_data(spec, state): rng = Random(1234) @@ -74,7 +74,7 @@ def test_simple_blob_data(spec, state): yield 'steps', test_steps -@with_all_phases_from_except(DENEB, [EIP7594]) +@with_all_phases_from_except(DENEB, [FULU]) @spec_state_test def test_invalid_incorrect_proof(spec, state): rng = Random(1234) @@ -102,7 +102,7 @@ def test_invalid_incorrect_proof(spec, state): yield 'steps', test_steps -@with_all_phases_from_except(DENEB, [EIP7594]) +@with_all_phases_from_except(DENEB, [FULU]) @spec_state_test def test_invalid_data_unavailable(spec, state): rng = Random(1234) @@ -130,7 +130,7 @@ def test_invalid_data_unavailable(spec, state): yield 'steps', test_steps -@with_all_phases_from_except(DENEB, [EIP7594]) +@with_all_phases_from_except(DENEB, [FULU]) @spec_state_test def test_invalid_wrong_proofs_length(spec, state): rng = Random(1234) @@ -158,7 +158,7 @@ def test_invalid_wrong_proofs_length(spec, state): yield 'steps', test_steps -@with_all_phases_from_except(DENEB, [EIP7594]) +@with_all_phases_from_except(DENEB, [FULU]) @spec_state_test def test_invalid_wrong_blobs_length(spec, state): rng = Random(1234) diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/__init__.py b/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_custody.py b/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_custody.py deleted file mode 100644 index 5db3635a8e..0000000000 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_custody.py +++ /dev/null @@ -1,53 +0,0 @@ -from eth2spec.test.context import ( - expect_assertion_error, - spec_test, - single_phase, - with_eip7594_and_later, -) - - -def run_get_custody_columns(spec, peer_count, custody_subnet_count): - assignments = [spec.get_custody_columns(node_id, custody_subnet_count) for node_id in range(peer_count)] - - columns_per_subnet = spec.config.NUMBER_OF_COLUMNS // spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT - for assignment in assignments: - assert len(assignment) == custody_subnet_count * columns_per_subnet - assert len(assignment) == len(set(assignment)) - - -@with_eip7594_and_later -@spec_test -@single_phase -def test_get_custody_columns_peers_within_number_of_columns(spec): - peer_count = 10 - custody_subnet_count = spec.config.CUSTODY_REQUIREMENT - assert spec.config.NUMBER_OF_COLUMNS > peer_count - run_get_custody_columns(spec, peer_count, custody_subnet_count) - - -@with_eip7594_and_later -@spec_test -@single_phase -def test_get_custody_columns_peers_more_than_number_of_columns(spec): - peer_count = 200 - custody_subnet_count = spec.config.CUSTODY_REQUIREMENT - assert spec.config.NUMBER_OF_COLUMNS < peer_count - run_get_custody_columns(spec, peer_count, custody_subnet_count) - - -@with_eip7594_and_later -@spec_test -@single_phase -def test_get_custody_columns_maximum_subnets(spec): - peer_count = 10 - custody_subnet_count = spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT - run_get_custody_columns(spec, peer_count, custody_subnet_count) - - -@with_eip7594_and_later -@spec_test -@single_phase -def test_get_custody_columns_custody_size_more_than_number_of_columns(spec): - node_id = 1 - custody_subnet_count = spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT + 1 - expect_assertion_error(lambda: spec.get_custody_columns(node_id, custody_subnet_count)) diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py index 8fdbb8e2e5..3703b9f0f5 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py @@ -11,6 +11,7 @@ ) from eth2spec.test.helpers.withdrawals import ( set_eth1_withdrawal_credential_with_balance, + set_compounding_withdrawal_credential_with_balance, set_compounding_withdrawal_credential, ) @@ -47,8 +48,8 @@ def test_basic_consolidation_in_current_consolidation_epoch(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) # Set earliest consolidation epoch to the expected exit epoch expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) @@ -96,59 +97,7 @@ def test_basic_consolidation_with_excess_target_balance(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) - - # Set earliest consolidation epoch to the expected exit epoch - expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) - state.earliest_consolidation_epoch = expected_exit_epoch - consolidation_churn_limit = spec.get_consolidation_churn_limit(state) - # Set the consolidation balance to consume equal to churn limit - state.consolidation_balance_to_consume = consolidation_churn_limit - - # Add excess balance - state.balances[target_index] = state.balances[target_index] + spec.EFFECTIVE_BALANCE_INCREMENT - - yield from run_consolidation_processing(spec, state, consolidation) - - # Check consolidation churn is decremented correctly - assert ( - state.consolidation_balance_to_consume - == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE - ) - # Check exit epoch - assert state.validators[source_index].exit_epoch == expected_exit_epoch - - -@with_electra_and_later -@with_presets([MINIMAL], "need sufficient consolidation churn limit") -@with_custom_state( - balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, - threshold_fn=default_activation_threshold, -) -@spec_test -@single_phase -def test_basic_consolidation_with_excess_target_balance_and_compounding_credentials(spec, state): - # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation - state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - # This state has 256 validators each with 32 ETH in MINIMAL preset, 128 ETH consolidation churn - current_epoch = spec.get_current_epoch(state) - source_index = spec.get_active_validator_indices(state, current_epoch)[0] - target_index = spec.get_active_validator_indices(state, current_epoch)[1] - - # Set source to eth1 credentials - source_address = b"\x22" * 20 - set_eth1_withdrawal_credential_with_balance( - spec, state, source_index, address=source_address - ) - # Make consolidation with source address - consolidation = spec.ConsolidationRequest( - source_address=source_address, - source_pubkey=state.validators[source_index].pubkey, - target_pubkey=state.validators[target_index].pubkey, - ) - - # Set target to eth1 credentials + # Set target to compounding credentials set_compounding_withdrawal_credential(spec, state, target_index) # Set earliest consolidation epoch to the expected exit epoch @@ -202,8 +151,8 @@ def test_basic_consolidation_in_new_consolidation_epoch(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) yield from run_consolidation_processing(spec, state, consolidation) @@ -247,8 +196,8 @@ def test_basic_consolidation_with_preexisting_churn(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) # Set earliest consolidation epoch to the expected exit epoch expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) @@ -296,8 +245,8 @@ def test_basic_consolidation_with_insufficient_preexisting_churn(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) # Set earliest consolidation epoch to the first available epoch state.earliest_consolidation_epoch = spec.compute_activation_exit_epoch( @@ -337,7 +286,7 @@ def test_basic_consolidation_with_compounding_credentials(spec, state): source_index = spec.get_active_validator_indices(state, current_epoch)[0] target_index = spec.get_active_validator_indices(state, current_epoch)[1] - # Set source to eth1 credentials + # Set source to compounding credentials source_address = b"\x22" * 20 set_compounding_withdrawal_credential( spec, state, source_index, address=source_address @@ -396,8 +345,8 @@ def test_consolidation_churn_limit_balance(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) # Set source effective balance to consolidation churn limit consolidation_churn_limit = spec.get_consolidation_churn_limit(state) @@ -446,8 +395,8 @@ def test_consolidation_balance_larger_than_churn_limit(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) # Set source effective balance to 2 * consolidation churn limit consolidation_churn_limit = spec.get_consolidation_churn_limit(state) @@ -495,8 +444,8 @@ def test_consolidation_balance_through_two_churn_epochs(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - # Set target to eth1 credentials - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set target to compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) # Set source balance higher to 3 * consolidation churn limit consolidation_churn_limit = spec.get_consolidation_churn_limit(state) @@ -625,7 +574,7 @@ def test_incorrect_exceed_pending_consolidations_limit(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the the return condition assert len(state.pending_consolidations) == spec.PENDING_CONSOLIDATIONS_LIMIT @@ -660,7 +609,7 @@ def test_incorrect_not_enough_consolidation_churn_available(spec, state): target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the the return condition assert spec.get_consolidation_churn_limit(state) <= spec.MIN_ACTIVATION_BALANCE @@ -694,7 +643,7 @@ def test_incorrect_exited_source(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # exit source spec.initiate_validator_exit(state, source_index) @@ -731,7 +680,7 @@ def test_incorrect_exited_target(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # exit target spec.initiate_validator_exit(state, 1) @@ -767,7 +716,7 @@ def test_incorrect_inactive_source(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # set source validator as not yet activated state.validators[source_index].activation_epoch = spec.FAR_FUTURE_EPOCH @@ -804,7 +753,7 @@ def test_incorrect_inactive_target(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # set target validator as not yet activated state.validators[1].activation_epoch = spec.FAR_FUTURE_EPOCH @@ -839,7 +788,7 @@ def test_incorrect_no_source_execution_withdrawal_credential(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the the return condition assert not spec.has_execution_withdrawal_credential(state.validators[source_index]) @@ -857,7 +806,7 @@ def test_incorrect_no_source_execution_withdrawal_credential(spec, state): ) @spec_test @single_phase -def test_incorrect_no_target_execution_withdrawal_credential(spec, state): +def test_incorrect_target_with_bls_credential(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # Set up a correct consolidation, but target does not have @@ -883,6 +832,39 @@ def test_incorrect_no_target_execution_withdrawal_credential(spec, state): ) +@with_electra_and_later +@with_presets([MINIMAL], "need sufficient consolidation churn limit") +@with_custom_state( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_target_with_eth1_credential(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + # Set up an otherwise correct consolidation + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, state, source_index, address=source_address + ) + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + # Set target to eth1 credentials + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + + yield from run_consolidation_processing( + spec, state, consolidation, success=False + ) + + @with_electra_and_later @with_presets([MINIMAL], "need sufficient consolidation churn limit") @with_custom_state( @@ -908,7 +890,7 @@ def test_incorrect_incorrect_source_address(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the the return condition assert not state.validators[source_index].withdrawal_credentials[12:] == consolidation.source_address @@ -943,7 +925,7 @@ def test_incorrect_unknown_source_pubkey(spec, state): source_pubkey=b"\x00" * 48, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the the return condition assert not state.validators[source_index].pubkey == consolidation.source_pubkey @@ -978,7 +960,7 @@ def test_incorrect_unknown_target_pubkey(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=b"\x00" * 48, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the the return condition assert not state.validators[target_index].pubkey == consolidation.target_pubkey @@ -1013,11 +995,11 @@ def test_incorrect_source_has_pending_withdrawal(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Create pending withdrawal pending_withdrawal = spec.PendingPartialWithdrawal( - index=0, amount=excess_balance, withdrawable_epoch=current_epoch + validator_index=0, amount=excess_balance, withdrawable_epoch=current_epoch ) state.pending_partial_withdrawals.append(pending_withdrawal) @@ -1052,7 +1034,7 @@ def test_incorrect_source_not_active_long_enough(spec, state): source_pubkey=state.validators[source_index].pubkey, target_pubkey=state.validators[target_index].pubkey, ) - set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) # Check the return condition assert current_epoch < state.validators[source_index].activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD @@ -1228,7 +1210,7 @@ def run_consolidation_processing(spec, state, consolidation, success=True): pre_exit_epoch_source = source_validator.exit_epoch pre_exit_epoch_target = target_validator.exit_epoch pre_pending_consolidations = state.pending_consolidations.copy() - pre_target_withdrawal_credentials = target_validator.withdrawal_credentials + pre_source_balance = state.balances[source_index] pre_target_balance = state.balances[target_index] else: pre_state = state.copy() @@ -1266,23 +1248,9 @@ def run_consolidation_processing(spec, state, consolidation, success=True): target_index=target_index, ) assert state.pending_consolidations == pre_pending_consolidations + [expected_new_pending_consolidation] - # Check excess balance is queued if the target switched to compounding - if pre_target_withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX: - post_target_withdrawal_credentials = ( - spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_target_withdrawal_credentials[1:] - ) - assert state.validators[target_index].withdrawal_credentials == post_target_withdrawal_credentials - assert state.balances[target_index] == spec.MIN_ACTIVATION_BALANCE - if pre_target_balance > spec.MIN_ACTIVATION_BALANCE: - assert len(state.pending_deposits) == 1 - pending_deposit = state.pending_deposits[0] - assert pending_deposit.pubkey == target_validator.pubkey - assert pending_deposit.withdrawal_credentials == post_target_withdrawal_credentials - assert pending_deposit.amount == (pre_target_balance - spec.MIN_ACTIVATION_BALANCE) - assert pending_deposit.signature == spec.G2_POINT_AT_INFINITY - assert pending_deposit.slot == spec.GENESIS_SLOT - else: - assert state.balances[target_index] == pre_target_balance + # Check no balance move happened + assert state.balances[source_index] == pre_source_balance + assert state.balances[target_index] == pre_target_balance else: assert pre_state == state diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_voluntary_exit.py index eb2d86e6f9..2f9cf81ca4 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_voluntary_exit.py @@ -401,7 +401,7 @@ def test_invalid_validator_has_pending_withdrawal(spec, state): state.pending_partial_withdrawals.append( spec.PendingPartialWithdrawal( - index=validator_index, + validator_index=validator_index, amount=1, withdrawable_epoch=spec.compute_activation_exit_epoch(current_epoch), ) diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py index 39626ee059..aba6a29332 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py @@ -13,6 +13,92 @@ set_eth1_withdrawal_credential_with_balance, set_compounding_withdrawal_credential, ) +# +# Run processing +# + + +def run_withdrawal_request_processing( + spec, state, withdrawal_request, valid=True, success=True +): + """ + Run ``process_withdrawal_request``, yielding: + - pre-state ('pre') + - withdrawal_request ('withdrawal_request') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + If ``success == False``, it doesn't initiate exit successfully + """ + yield "pre", state + yield "withdrawal_request", withdrawal_request + + if not valid: + expect_assertion_error( + lambda: spec.process_withdrawal_request( + state, withdrawal_request + ) + ) + yield "post", None + return + + pre_state = state.copy() + + spec.process_withdrawal_request( + state, withdrawal_request + ) + + yield "post", state + + if not success: + # No-op + assert pre_state == state + else: + validator_index = get_validator_index_by_pubkey( + state, withdrawal_request.validator_pubkey + ) + pre_exit_epoch = pre_state.validators[validator_index].exit_epoch + pre_pending_partial_withdrawals = pre_state.pending_partial_withdrawals.copy() + pre_balance = pre_state.balances[validator_index] + pre_effective_balance = pre_state.validators[validator_index].effective_balance + assert state.balances[validator_index] == pre_balance + assert ( + state.validators[validator_index].effective_balance == pre_effective_balance + ) + # Full exit request + if withdrawal_request.amount == spec.FULL_EXIT_REQUEST_AMOUNT: + assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.get_pending_balance_to_withdraw(state, validator_index) == 0 + assert state.pending_partial_withdrawals == pre_pending_partial_withdrawals + # Partial withdrawal request + else: + expected_amount_to_withdraw = compute_amount_to_withdraw( + spec, pre_state, validator_index, withdrawal_request.amount + ) + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + expected_withdrawable_epoch = ( + state.earliest_exit_epoch + + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + expected_partial_withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=expected_amount_to_withdraw, + withdrawable_epoch=expected_withdrawable_epoch, + ) + assert ( + state.pending_partial_withdrawals + == pre_pending_partial_withdrawals + [expected_partial_withdrawal] + ) + + +def compute_amount_to_withdraw(spec, state, index, amount): + pending_balance_to_withdraw = spec.get_pending_balance_to_withdraw(state, index) + return min( + state.balances[index] + - spec.MIN_ACTIVATION_BALANCE + - pending_balance_to_withdraw, + amount, + ) # Modified tests from 7002. Just testing EL-triggered exits, not partial withdrawals @@ -110,7 +196,7 @@ def test_basic_withdrawal_request_with_full_partial_withdrawal_queue(spec, state # Fill the partial withdrawal queue to the max (with a different validator index) partial_withdrawal = spec.PendingPartialWithdrawal( - index=1, amount=1, withdrawable_epoch=current_epoch + validator_index=1, amount=1, withdrawable_epoch=current_epoch ) state.pending_partial_withdrawals = [ partial_withdrawal @@ -385,7 +471,7 @@ def test_partial_withdrawal_request_with_pending_withdrawals(spec, state): # Add pending withdrawals partial_withdrawal = spec.PendingPartialWithdrawal( - index=validator_index, amount=amount, withdrawable_epoch=current_epoch + validator_index=validator_index, amount=amount, withdrawable_epoch=current_epoch ) state.pending_partial_withdrawals = [partial_withdrawal] * 2 @@ -427,7 +513,7 @@ def test_partial_withdrawal_request_with_pending_withdrawals_and_high_amount( # Add many pending withdrawals partial_withdrawal = spec.PendingPartialWithdrawal( - index=validator_index, + validator_index=validator_index, amount=spec.EFFECTIVE_BALANCE_INCREMENT, withdrawable_epoch=current_epoch, ) @@ -575,7 +661,7 @@ def test_partial_withdrawal_queue_full(spec, state): # Fill the partial withdrawal queue to the max partial_withdrawal = spec.PendingPartialWithdrawal( - index=1, amount=1, withdrawable_epoch=current_epoch + validator_index=1, amount=1, withdrawable_epoch=current_epoch ) state.pending_partial_withdrawals = [ partial_withdrawal @@ -660,7 +746,7 @@ def test_pending_withdrawals_consume_all_excess_balance(spec, state): # Add pending withdrawals totalling an amount equal to the excess balance partial_withdrawal = spec.PendingPartialWithdrawal( - index=validator_index, amount=amount, withdrawable_epoch=current_epoch + validator_index=validator_index, amount=amount, withdrawable_epoch=current_epoch ) state.pending_partial_withdrawals = [partial_withdrawal] * 10 @@ -865,7 +951,7 @@ def test_full_exit_request_has_partial_withdrawal(spec, state): state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA state.pending_partial_withdrawals.append( spec.PendingPartialWithdrawal( - index=validator_index, + validator_index=validator_index, amount=1, withdrawable_epoch=spec.compute_activation_exit_epoch(current_epoch), ) @@ -887,12 +973,11 @@ def test_incorrect_inactive_validator(spec, state): validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) validator_pubkey = state.validators[validator_index].pubkey address = b"\x22" * 20 - incorrect_address = b"\x33" * 20 set_eth1_withdrawal_credential_with_balance( spec, state, validator_index, address=address ) withdrawal_request = spec.WithdrawalRequest( - source_address=incorrect_address, + source_address=address, validator_pubkey=validator_pubkey, amount=spec.FULL_EXIT_REQUEST_AMOUNT, ) @@ -904,90 +989,3 @@ def test_incorrect_inactive_validator(spec, state): yield from run_withdrawal_request_processing( spec, state, withdrawal_request, success=False ) - -# -# Run processing -# - - -def run_withdrawal_request_processing( - spec, state, withdrawal_request, valid=True, success=True -): - """ - Run ``process_withdrawal_request``, yielding: - - pre-state ('pre') - - withdrawal_request ('withdrawal_request') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - If ``success == False``, it doesn't initiate exit successfully - """ - yield "pre", state - yield "withdrawal_request", withdrawal_request - - if not valid: - expect_assertion_error( - lambda: spec.process_withdrawal_request( - state, withdrawal_request - ) - ) - yield "post", None - return - - pre_state = state.copy() - - spec.process_withdrawal_request( - state, withdrawal_request - ) - - yield "post", state - - if not success: - # No-op - assert pre_state == state - else: - validator_index = get_validator_index_by_pubkey( - state, withdrawal_request.validator_pubkey - ) - pre_exit_epoch = pre_state.validators[validator_index].exit_epoch - pre_pending_partial_withdrawals = pre_state.pending_partial_withdrawals.copy() - pre_balance = pre_state.balances[validator_index] - pre_effective_balance = pre_state.validators[validator_index].effective_balance - assert state.balances[validator_index] == pre_balance - assert ( - state.validators[validator_index].effective_balance == pre_effective_balance - ) - # Full exit request - if withdrawal_request.amount == spec.FULL_EXIT_REQUEST_AMOUNT: - assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH - assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH - assert spec.get_pending_balance_to_withdraw(state, validator_index) == 0 - assert state.pending_partial_withdrawals == pre_pending_partial_withdrawals - # Partial withdrawal request - else: - expected_amount_to_withdraw = compute_amount_to_withdraw( - spec, pre_state, validator_index, withdrawal_request.amount - ) - assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH - expected_withdrawable_epoch = ( - state.earliest_exit_epoch - + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - ) - expected_partial_withdrawal = spec.PendingPartialWithdrawal( - index=validator_index, - amount=expected_amount_to_withdraw, - withdrawable_epoch=expected_withdrawable_epoch, - ) - assert ( - state.pending_partial_withdrawals - == pre_pending_partial_withdrawals + [expected_partial_withdrawal] - ) - - -def compute_amount_to_withdraw(spec, state, index, amount): - pending_balance_to_withdraw = spec.get_pending_balance_to_withdraw(state, index) - return min( - state.balances[index] - - spec.MIN_ACTIVATION_BALANCE - - pending_balance_to_withdraw, - amount, - ) diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py index 1757c79994..ed3d914154 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py @@ -91,14 +91,14 @@ def test_success_excess_balance_but_no_max_effective_balance_compounding(spec, s @with_electra_and_later @spec_state_test def test_pending_withdrawals_one_skipped_one_effective(spec, state): - index_0 = 3 - index_1 = 5 + validator_index_0 = 3 + validator_index_1 = 5 - pending_withdrawal_0 = prepare_pending_withdrawal(spec, state, index_0) - pending_withdrawal_1 = prepare_pending_withdrawal(spec, state, index_1) + pending_withdrawal_0 = prepare_pending_withdrawal(spec, state, validator_index_0) + pending_withdrawal_1 = prepare_pending_withdrawal(spec, state, validator_index_1) # If validator doesn't have an excess balance pending withdrawal is skipped - state.balances[index_0] = spec.MIN_ACTIVATION_BALANCE + state.balances[validator_index_0] = spec.MIN_ACTIVATION_BALANCE execution_payload = build_empty_execution_payload(spec, state) assert state.pending_partial_withdrawals == [pending_withdrawal_0, pending_withdrawal_1] @@ -155,7 +155,7 @@ def test_pending_withdrawals_exiting_validator(spec, state): validator_index = len(state.validators) // 2 pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index) - spec.initiate_validator_exit(state, pending_withdrawal.index) + spec.initiate_validator_exit(state, pending_withdrawal.validator_index) execution_payload = build_empty_execution_payload(spec, state) yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0) @@ -169,7 +169,7 @@ def test_pending_withdrawals_low_effective_balance(spec, state): validator_index = len(state.validators) // 2 pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index) - state.validators[pending_withdrawal.index].effective_balance = ( + state.validators[pending_withdrawal.validator_index].effective_balance = ( spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT ) @@ -185,7 +185,7 @@ def test_pending_withdrawals_no_excess_balance(spec, state): validator_index = len(state.validators) // 2 pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index) - state.balances[pending_withdrawal.index] = spec.MIN_ACTIVATION_BALANCE + state.balances[pending_withdrawal.validator_index] = spec.MIN_ACTIVATION_BALANCE execution_payload = build_empty_execution_payload(spec, state) yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0) diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py index 0c920d56a3..ccbfbc13a3 100644 --- a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py @@ -308,7 +308,15 @@ def test_apply_pending_deposit_top_up__zero_balance(spec, state): def test_apply_pending_deposit_incorrect_sig_top_up(spec, state): validator_index = 0 amount = spec.MIN_ACTIVATION_BALANCE // 4 - pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=False) + + # ensure the deposit signature is incorrect + assert not spec.is_valid_deposit_signature( + pending_deposit.pubkey, + pending_deposit.withdrawal_credentials, + pending_deposit.amount, + pending_deposit.signature + ) # invalid signatures, in top-ups, are allowed! yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py index b061efee7b..3dac3040d9 100644 --- a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py @@ -239,12 +239,13 @@ def test_pending_consolidation_compounding_creds(spec, state): # Pending consolidation was successfully processed expected_target_balance = ( - state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index] + spec.MIN_ACTIVATION_BALANCE + state_before_consolidation.balances[target_index] ) assert state.balances[target_index] == expected_target_balance # All source balance is active and moved to the target, # because the source validator has compounding credentials - assert state.balances[source_index] == 0 + assert state.balances[source_index] == ( + state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE) assert state.pending_consolidations == [] # Pending balance deposit to the target is not created, @@ -297,10 +298,11 @@ def test_pending_consolidation_with_pending_deposit(spec, state): # Pending consolidation was successfully processed expected_target_balance = ( - state_before_consolidation.balances[source_index] + state_before_consolidation.balances[target_index] + spec.MIN_ACTIVATION_BALANCE + state_before_consolidation.balances[target_index] ) assert state.balances[target_index] == expected_target_balance - assert state.balances[source_index] == 0 + assert state.balances[source_index] == ( + state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE) assert state.pending_consolidations == [] # Pending deposit to the source was not processed. @@ -434,3 +436,101 @@ def test_pending_consolidation_source_balance_greater_than_max_effective_compoun assert state.balances[target_index] == pre_balance_target + source_max_effective_balance assert state.balances[source_index] == excess_source_balance assert state.pending_consolidations == [] + + +# ******************************* +# * CONSOLIDATION BALANCE TESTS * +# ******************************* + + +def prepare_consolidation_and_state(spec, state, source_index, target_index, + creds_type, balance_to_eb, eb_to_min_ab, eb_to_max_eb): + assert creds_type in ['comp', 'eth1'] + assert balance_to_eb in ['<', '=', '>'] + assert eb_to_min_ab in ['<', '=', '>'] + assert eb_to_max_eb in ['<', '='] + if creds_type == 'eth1': + assert eb_to_min_ab == eb_to_max_eb + else: + assert (eb_to_min_ab, eb_to_max_eb) in [('<', '<'), ('=', '<'), ('>', '<'), ('>', '=')] + + # append pending consolidation + current_epoch = spec.get_current_epoch(state) + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source_index, target_index=target_index) + ) + # Set withdrawable epoch to current epoch to allow processing + state.validators[source_index].withdrawable_epoch = current_epoch + + # Set source and target withdrawal credentials + if creds_type == 'eth1': + set_eth1_withdrawal_credential_with_balance(spec, state, source_index) + else: + set_compounding_withdrawal_credential_with_balance(spec, state, source_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Set source balances + source = state.validators[source_index] + max_eb = spec.get_max_effective_balance(source) + if eb_to_min_ab == '<': + source.effective_balance = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + elif eb_to_min_ab == '=': + source.effective_balance = spec.MIN_ACTIVATION_BALANCE + elif eb_to_max_eb == '<': + source.effective_balance = (max_eb - spec.MIN_ACTIVATION_BALANCE) // 2 + else: + # eb_to_max_eb == '=' + source.effective_balance = max_eb + + if balance_to_eb == '<': + state.balances[source_index] = source.effective_balance - spec.EFFECTIVE_BALANCE_INCREMENT // 2 + elif balance_to_eb == '=': + state.balances[source_index] = source.effective_balance + else: + state.balances[source_index] = source.effective_balance + spec.EFFECTIVE_BALANCE_INCREMENT // 2 + + +def run_balance_computation_test(spec, state, instance_tuples): + max_index = 0 + for creds_type, balance_to_eb, eb_to_min_ab, eb_to_max_eb in instance_tuples: + source_index = max_index + target_index = max_index + 1 + prepare_consolidation_and_state( + spec, state, source_index, target_index, creds_type, balance_to_eb, eb_to_min_ab, eb_to_max_eb + ) + max_index += 2 + + pre_state = state.copy() + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Check balances are moved correctly + for source_index in range(0, max_index, 2): + target_index = source_index + 1 + consolidated_balance = min( + pre_state.validators[source_index].effective_balance, pre_state.balances[source_index] + ) + assert state.balances[source_index] == pre_state.balances[source_index] - consolidated_balance + assert state.balances[target_index] == pre_state.balances[target_index] + consolidated_balance + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_balance_computation_eth1(spec, state): + instances = [] + for balance_to_eb in ['<', '=', '>']: + for eb_to_min_ab, eb_to_max_eb in [('<', '<'), ('=', '=')]: + instances.append(('eth1', balance_to_eb, eb_to_min_ab, eb_to_max_eb)) + + yield from run_balance_computation_test(spec, state, instances) + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_balance_computation_compounding(spec, state): + instances = [] + for balance_to_eb in ['<', '=', '>']: + for eb_to_min_ab, eb_to_max_eb in [('<', '<'), ('=', '<'), ('>', '<'), ('>', '=')]: + instances.append(('comp', balance_to_eb, eb_to_min_ab, eb_to_max_eb)) + + yield from run_balance_computation_test(spec, state, instances) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/test_slots.py b/tests/core/pyspec/eth2spec/test/electra/sanity/test_slots.py new file mode 100644 index 0000000000..9ea506392b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/test_slots.py @@ -0,0 +1,134 @@ +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.deposits import prepare_pending_deposit +from eth2spec.test.helpers.state import transition_to + + +def run_epoch_processing(spec, state, pending_deposits=None, pending_consolidations=None): + if pending_deposits is None: + pending_deposits = [] + if pending_consolidations is None: + pending_consolidations = [] + # Transition to the last slot of the epoch + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1 + transition_to(spec, state, slot) + state.pending_deposits = pending_deposits + state.pending_consolidations = pending_consolidations + yield 'pre', state + yield 'slots', 1 + spec.process_slots(state, state.slot + 1) + yield 'post', state + + assert state.pending_deposits == [] + assert state.pending_consolidations == [] + + +@with_electra_and_later +@spec_state_test +def test_multiple_pending_deposits_same_pubkey(spec, state): + # Create multiple deposits with the same pubkey + index = len(state.validators) + deposit = prepare_pending_deposit(spec, validator_index=index, amount=spec.MIN_ACTIVATION_BALANCE, signed=True) + pending_deposits = [deposit, deposit] + + yield from run_epoch_processing(spec, state, pending_deposits=pending_deposits) + + # Check deposit balance is applied correctly + assert state.balances[index] == sum(d.amount for d in pending_deposits) + assert state.validators[index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_multiple_pending_deposits_same_pubkey_compounding(spec, state): + # Create multiple deposits with the same pubkey and compounding creds + index = len(state.validators) + deposit = prepare_pending_deposit( + spec, validator_index=index, amount=spec.MIN_ACTIVATION_BALANCE, signed=True, + withdrawal_credentials=(spec.COMPOUNDING_WITHDRAWAL_PREFIX + b'\x00' * 11 + b'\x11' * 20) + ) + pending_deposits = [deposit, deposit] + + yield from run_epoch_processing(spec, state, pending_deposits=pending_deposits) + + # Check deposit balance is applied correctly + assert state.balances[index] == sum(d.amount for d in pending_deposits) + assert state.validators[index].effective_balance == state.balances[index] + + +@with_electra_and_later +@spec_state_test +def test_multiple_pending_deposits_same_pubkey_below_upward_threshold(spec, state): + # Create multiple deposits with top up lower than the upward threshold + index = len(state.validators) + deposit_0 = prepare_pending_deposit( + spec, validator_index=index, + amount=(spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT), signed=True + ) + deposit_1 = prepare_pending_deposit( + spec, validator_index=index, + amount=spec.EFFECTIVE_BALANCE_INCREMENT, signed=True + ) + pending_deposits = [deposit_0, deposit_1] + + yield from run_epoch_processing(spec, state, pending_deposits=pending_deposits) + + # Check deposit balance is applied correctly + assert state.balances[index] == sum(d.amount for d in pending_deposits) + assert state.validators[index].effective_balance == deposit_0.amount + + +@with_electra_and_later +@spec_state_test +def test_multiple_pending_deposits_same_pubkey_above_upward_threshold(spec, state): + # Create multiple deposits with top up greater than the upward threshold + index = len(state.validators) + deposit_0 = prepare_pending_deposit( + spec, validator_index=index, + amount=(spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT), signed=True + ) + amount = spec.EFFECTIVE_BALANCE_INCREMENT // spec.HYSTERESIS_QUOTIENT * spec.HYSTERESIS_UPWARD_MULTIPLIER + 1 + deposit_1 = prepare_pending_deposit(spec, validator_index=index, amount=amount, signed=True) + pending_deposits = [deposit_0, deposit_1] + + yield from run_epoch_processing(spec, state, pending_deposits) + + # Check deposit balance is applied correctly + balance = state.balances[index] + assert balance == sum(d.amount for d in pending_deposits) + assert state.validators[index].effective_balance == balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation(spec, state): + # Create pending consolidation + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + # Set withdrawable epoch to current epoch to allow processing + state.validators[source_index].withdrawable_epoch = current_epoch + # Set the source withdrawal credential to eth1 + state.validators[target_index].withdrawal_credentials = ( + spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + ) + # Set the target withdrawal credential to compounding + state.validators[target_index].withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + ) + pending_consolidations = [spec.PendingConsolidation(source_index=source_index, target_index=target_index)] + + assert state.balances[source_index] == spec.MIN_ACTIVATION_BALANCE + assert state.validators[source_index].effective_balance == spec.MIN_ACTIVATION_BALANCE + assert state.balances[target_index] == spec.MIN_ACTIVATION_BALANCE + assert state.validators[target_index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + yield from run_epoch_processing(spec, state, pending_consolidations=pending_consolidations) + + # Check the consolidation is processed correctly + assert state.balances[source_index] == 0 + assert state.validators[source_index].effective_balance == 0 + assert state.balances[target_index] == spec.MIN_ACTIVATION_BALANCE * 2 + assert state.validators[target_index].effective_balance == spec.MIN_ACTIVATION_BALANCE * 2 diff --git a/tests/core/pyspec/eth2spec/test/electra/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/electra/unittests/test_config_invariants.py index 839fa0dbd4..ce4b6684a4 100644 --- a/tests/core/pyspec/eth2spec/test/electra/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/electra/unittests/test_config_invariants.py @@ -13,3 +13,16 @@ def test_processing_pending_partial_withdrawals(spec): spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP < spec.MAX_WITHDRAWALS_PER_PAYLOAD ) + + +@with_electra_and_later +@spec_test +@single_phase +def test_networking(spec): + assert spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK + assert ( + spec.config.MAX_REQUEST_BLOB_SIDECARS_ELECTRA == + spec.config.MAX_REQUEST_BLOCKS_DENEB * spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA + ) + # Start with the same size, but `BLOB_SIDECAR_SUBNET_COUNT` could potentially increase later. + assert spec.config.BLOB_SIDECAR_SUBNET_COUNT_ELECTRA == spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/genesis/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/bellatrix/genesis/__init__.py rename to tests/core/pyspec/eth2spec/test/fulu/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/eip7594/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/eip7594/__init__.py rename to tests/core/pyspec/eth2spec/test/fulu/merkle_proof/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/eip7594/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py similarity index 97% rename from tests/core/pyspec/eth2spec/test/eip7594/merkle_proof/test_single_merkle_proof.py rename to tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py index 4bd5fb4912..b614793f73 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/merkle_proof/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py @@ -2,7 +2,7 @@ from eth2spec.test.context import ( spec_state_test, - with_eip7594_and_later, + with_fulu_and_later, with_test_suite_name, ) from eth2spec.test.helpers.block import ( @@ -62,14 +62,14 @@ def _run_blob_kzg_commitments_merkle_proof_test(spec, state, rng=None): @with_test_suite_name("BeaconBlockBody") -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__basic(spec, state): yield from _run_blob_kzg_commitments_merkle_proof_test(spec, state) @with_test_suite_name("BeaconBlockBody") -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test def test_blob_kzg_commitments_merkle_proof__random_block_1(spec, state): rng = random.Random(1111) diff --git a/tests/core/pyspec/eth2spec/test/eip7594/merkle_proof/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/networking/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/eip7594/merkle_proof/__init__.py rename to tests/core/pyspec/eth2spec/test/fulu/networking/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/eip7594/networking/test_get_custody_columns.py b/tests/core/pyspec/eth2spec/test/fulu/networking/test_get_custody_columns.py similarity index 51% rename from tests/core/pyspec/eth2spec/test/eip7594/networking/test_get_custody_columns.py rename to tests/core/pyspec/eth2spec/test/fulu/networking/test_get_custody_columns.py index 3e1013734d..d3be42ce16 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/networking/test_get_custody_columns.py +++ b/tests/core/pyspec/eth2spec/test/fulu/networking/test_get_custody_columns.py @@ -3,88 +3,93 @@ from eth2spec.test.context import ( single_phase, spec_test, - with_eip7594_and_later, + with_fulu_and_later, ) -def _run_get_custody_columns(spec, rng, node_id=None, custody_subnet_count=None): +def _run_get_custody_columns(spec, rng, node_id=None, custody_group_count=None): if node_id is None: node_id = rng.randint(0, 2**256 - 1) - if custody_subnet_count is None: - custody_subnet_count = rng.randint(0, spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT) + if custody_group_count is None: + custody_group_count = rng.randint(0, spec.config.NUMBER_OF_CUSTODY_GROUPS) - result = spec.get_custody_columns(node_id, custody_subnet_count) + columns_per_group = spec.config.NUMBER_OF_COLUMNS // spec.config.NUMBER_OF_CUSTODY_GROUPS + groups = spec.get_custody_groups(node_id, custody_group_count) yield 'node_id', 'meta', node_id - yield 'custody_subnet_count', 'meta', int(custody_subnet_count) + yield 'custody_group_count', 'meta', int(custody_group_count) + + result = [] + for group in groups: + group_columns = spec.compute_columns_for_custody_group(group) + assert len(group_columns) == columns_per_group + result.extend(group_columns) assert len(result) == len(set(result)) - assert len(result) == ( - custody_subnet_count * spec.config.NUMBER_OF_COLUMNS // spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT - ) + assert len(result) == custody_group_count * columns_per_group assert all(i < spec.config.NUMBER_OF_COLUMNS for i in result) python_list_result = [int(i) for i in result] yield 'result', 'meta', python_list_result -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase -def test_get_custody_columns__min_node_id_min_custody_subnet_count(spec): +def test_get_custody_columns__min_node_id_min_custody_group_count(spec): rng = random.Random(1111) - yield from _run_get_custody_columns(spec, rng, node_id=0, custody_subnet_count=0) + yield from _run_get_custody_columns(spec, rng, node_id=0, custody_group_count=0) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase -def test_get_custody_columns__min_node_id_max_custody_subnet_count(spec): +def test_get_custody_columns__min_node_id_max_custody_group_count(spec): rng = random.Random(1111) yield from _run_get_custody_columns( spec, rng, node_id=0, - custody_subnet_count=spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT) + custody_group_count=spec.config.NUMBER_OF_CUSTODY_GROUPS) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase -def test_get_custody_columns__max_node_id_min_custody_subnet_count(spec): +def test_get_custody_columns__max_node_id_min_custody_group_count(spec): rng = random.Random(1111) - yield from _run_get_custody_columns(spec, rng, node_id=2**256 - 1, custody_subnet_count=0) + yield from _run_get_custody_columns(spec, rng, node_id=2**256 - 1, custody_group_count=0) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase -def test_get_custody_columns__max_node_id_max_custody_subnet_count(spec): +def test_get_custody_columns__max_node_id_max_custody_group_count(spec): rng = random.Random(1111) yield from _run_get_custody_columns( spec, rng, node_id=2**256 - 1, - custody_subnet_count=spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT, + custody_group_count=spec.config.NUMBER_OF_CUSTODY_GROUPS, ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase -def test_get_custody_columns__max_node_id_max_custody_subnet_count_minus_1(spec): +def test_get_custody_columns__max_node_id_max_custody_group_count_minus_1(spec): rng = random.Random(1111) yield from _run_get_custody_columns( spec, rng, node_id=2**256 - 2, - custody_subnet_count=spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT, + custody_group_count=spec.config.NUMBER_OF_CUSTODY_GROUPS, ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_custody_columns__short_node_id(spec): rng = random.Random(1111) - yield from _run_get_custody_columns(spec, rng, node_id=1048576, custody_subnet_count=1) + yield from _run_get_custody_columns(spec, rng, node_id=1048576, custody_group_count=1) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_custody_columns__1(spec): @@ -92,7 +97,7 @@ def test_get_custody_columns__1(spec): yield from _run_get_custody_columns(spec, rng) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_custody_columns__2(spec): @@ -100,7 +105,7 @@ def test_get_custody_columns__2(spec): yield from _run_get_custody_columns(spec, rng) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_custody_columns__3(spec): diff --git a/tests/core/pyspec/eth2spec/test/eip7594/networking/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/eip7594/networking/__init__.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/__init__.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/das/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py similarity index 94% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/das/test_das.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py index dd0a80fda4..f18e52b149 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/das/test_das.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -4,7 +4,7 @@ spec_test, single_phase, with_config_overrides, - with_eip7594_and_later, + with_fulu_and_later, ) from eth2spec.test.helpers.blob import ( get_sample_blob, @@ -16,7 +16,7 @@ def chunks(lst, n): return [lst[i:i + n] for i in range(0, len(lst), n)] -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_compute_matrix(spec): @@ -41,7 +41,7 @@ def test_compute_matrix(spec): assert blob == input_blobs[blob_index] -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_recover_matrix(spec): @@ -68,7 +68,7 @@ def test_recover_matrix(spec): assert recovered_matrix == matrix -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_extended_sample_count__1(spec): @@ -77,7 +77,7 @@ def test_get_extended_sample_count__1(spec): spec.get_extended_sample_count(allowed_failures) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_extended_sample_count__2(spec): @@ -86,7 +86,7 @@ def test_get_extended_sample_count__2(spec): spec.get_extended_sample_count(allowed_failures) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_extended_sample_count__3(spec): @@ -95,7 +95,7 @@ def test_get_extended_sample_count__3(spec): spec.get_extended_sample_count(allowed_failures) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_extended_sample_count__lower_bound(spec): @@ -103,7 +103,7 @@ def test_get_extended_sample_count__lower_bound(spec): spec.get_extended_sample_count(allowed_failures) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_extended_sample_count__upper_bound(spec): @@ -111,7 +111,7 @@ def test_get_extended_sample_count__upper_bound(spec): spec.get_extended_sample_count(allowed_failures) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_get_extended_sample_count__upper_bound_exceed(spec): @@ -119,7 +119,7 @@ def test_get_extended_sample_count__upper_bound_exceed(spec): expect_assertion_error(lambda: spec.get_extended_sample_count(allowed_failures)) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @with_config_overrides({ 'NUMBER_OF_COLUMNS': 128, diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/das/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/das/__init__.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/test_polynomial_commitments.py similarity index 97% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/test_polynomial_commitments.py index 1d72b142af..b935c212e4 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/polynomial_commitments/test_polynomial_commitments.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -3,7 +3,7 @@ spec_test, single_phase, expect_assertion_error, - with_eip7594_and_later, + with_fulu_and_later, ) from eth2spec.test.helpers.blob import ( get_sample_blob, @@ -11,7 +11,7 @@ from eth2spec.utils.bls import BLS_MODULUS -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_fft(spec): @@ -45,7 +45,7 @@ def test_fft(spec): assert individual_evaluation == poly_eval[i] -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_coset_fft(spec): @@ -84,7 +84,7 @@ def test_coset_fft(spec): assert individual_evaluation == poly_eval[i] -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_construct_vanishing_polynomial(spec): @@ -108,7 +108,7 @@ def test_construct_vanishing_polynomial(spec): assert all(a != spec.BLSFieldElement(0) for a in zero_poly_eval_brp[start:end]) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_verify_cell_kzg_proof_batch_zero_cells(spec): @@ -121,7 +121,7 @@ def test_verify_cell_kzg_proof_batch_zero_cells(spec): ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_verify_cell_kzg_proof_batch(spec): @@ -173,7 +173,7 @@ def test_verify_cell_kzg_proof_batch(spec): ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_verify_cell_kzg_proof_batch_invalid(spec): @@ -229,7 +229,7 @@ def test_verify_cell_kzg_proof_batch_invalid(spec): ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_recover_cells_and_kzg_proofs(spec): @@ -268,7 +268,7 @@ def test_recover_cells_and_kzg_proofs(spec): assert proofs == recovered_proofs -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_multiply_polynomial_degree_overflow(spec): diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py similarity index 69% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/test_config_invariants.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py index 8d14f4ae1c..fcf98c7e75 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -1,11 +1,11 @@ from eth2spec.test.context import ( single_phase, spec_test, - with_eip7594_and_later, + with_fulu_and_later, ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_invariants(spec): @@ -20,21 +20,19 @@ def test_invariants(spec): ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_polynomical_commitments_sampling(spec): assert spec.FIELD_ELEMENTS_PER_EXT_BLOB == 2 * spec.FIELD_ELEMENTS_PER_BLOB -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_networking(spec): - assert spec.config.MAX_BLOBS_PER_BLOCK_EIP7594 <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK + assert spec.config.MAX_BLOBS_PER_BLOCK_FULU <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK assert ( - spec.config.MAX_REQUEST_BLOB_SIDECARS_EIP7594 == - spec.config.MAX_REQUEST_BLOCKS_DENEB * spec.config.MAX_BLOBS_PER_BLOCK_EIP7594 + spec.config.MAX_REQUEST_BLOB_SIDECARS_FULU == + spec.config.MAX_REQUEST_BLOCKS_DENEB * spec.config.MAX_BLOBS_PER_BLOCK_FULU ) - # Start with the same size, but `BLOB_SIDECAR_SUBNET_COUNT` could potentially increase later. - assert spec.config.BLOB_SIDECAR_SUBNET_COUNT_EIP7594 == spec.config.MAX_BLOBS_PER_BLOCK_EIP7594 diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_custody.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_custody.py new file mode 100644 index 0000000000..fa2fcbc7dc --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_custody.py @@ -0,0 +1,66 @@ +from eth2spec.test.context import ( + expect_assertion_error, + spec_test, + single_phase, + with_fulu_and_later, +) + + +def run_get_custody_columns(spec, peer_count, custody_group_count): + assignments = [spec.get_custody_groups(node_id, custody_group_count) for node_id in range(peer_count)] + + columns_per_group = spec.config.NUMBER_OF_COLUMNS // spec.config.NUMBER_OF_CUSTODY_GROUPS + for assignment in assignments: + columns = [] + for group in assignment: + group_columns = spec.compute_columns_for_custody_group(group) + assert len(group_columns) == columns_per_group + columns.extend(group_columns) + + assert len(columns) == custody_group_count * columns_per_group + assert len(columns) == len(set(columns)) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_columns_peers_within_number_of_columns(spec): + peer_count = 10 + custody_group_count = spec.config.CUSTODY_REQUIREMENT + assert spec.config.NUMBER_OF_COLUMNS > peer_count + run_get_custody_columns(spec, peer_count, custody_group_count) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_columns_peers_more_than_number_of_columns(spec): + peer_count = 200 + custody_group_count = spec.config.CUSTODY_REQUIREMENT + assert spec.config.NUMBER_OF_COLUMNS < peer_count + run_get_custody_columns(spec, peer_count, custody_group_count) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_columns_maximum_groups(spec): + peer_count = 10 + custody_group_count = spec.config.NUMBER_OF_CUSTODY_GROUPS + run_get_custody_columns(spec, peer_count, custody_group_count) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_columns_custody_size_more_than_number_of_groups(spec): + node_id = 1 + custody_group_count = spec.config.NUMBER_OF_CUSTODY_GROUPS + 1 + expect_assertion_error(lambda: spec.get_custody_groups(node_id, custody_group_count)) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_columns_for_custody_group_out_of_bound_custody_group(spec): + expect_assertion_error(lambda: spec.compute_columns_for_custody_group(spec.config.NUMBER_OF_CUSTODY_GROUPS)) diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_networking.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py similarity index 93% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/test_networking.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py index 931cc9c1d1..cb4a9e0f92 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_networking.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py @@ -3,7 +3,7 @@ single_phase, spec_state_test, spec_test, - with_eip7594_and_later, + with_fulu_and_later, ) from eth2spec.debug.random_value import ( RandomizationMode, @@ -45,7 +45,7 @@ def compute_data_column_sidecar(spec, state): # Tests for verify_data_column_sidecar -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar__valid(spec, state): @@ -53,7 +53,7 @@ def test_verify_data_column_sidecar__valid(spec, state): assert spec.verify_data_column_sidecar(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar__invalid_zero_blobs(spec, state): @@ -64,7 +64,7 @@ def test_verify_data_column_sidecar__invalid_zero_blobs(spec, state): assert not spec.verify_data_column_sidecar(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar__invalid_index(spec, state): @@ -73,7 +73,7 @@ def test_verify_data_column_sidecar__invalid_index(spec, state): assert not spec.verify_data_column_sidecar(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar__invalid_mismatch_len_column(spec, state): @@ -82,7 +82,7 @@ def test_verify_data_column_sidecar__invalid_mismatch_len_column(spec, state): assert not spec.verify_data_column_sidecar(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar__invalid_mismatch_len_kzg_commitments(spec, state): @@ -91,7 +91,7 @@ def test_verify_data_column_sidecar__invalid_mismatch_len_kzg_commitments(spec, assert not spec.verify_data_column_sidecar(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecars__invalid_mismatch_len_kzg_proofs(spec, state): @@ -103,7 +103,7 @@ def test_verify_data_column_sidecars__invalid_mismatch_len_kzg_proofs(spec, stat # Tests for verify_data_column_sidecar_kzg_proofs -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_kzg_proofs__valid(spec, state): @@ -111,7 +111,7 @@ def test_verify_data_column_sidecar_kzg_proofs__valid(spec, state): assert spec.verify_data_column_sidecar_kzg_proofs(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_column(spec, state): @@ -120,7 +120,7 @@ def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_column(spec, state assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_commitment(spec, state): @@ -129,7 +129,7 @@ def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_commitment(spec, s assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_proof(spec, state): @@ -141,7 +141,7 @@ def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_proof(spec, state) # Tests for verify_data_column_sidecar_inclusion_proof -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_inclusion_proof__valid(spec, state): @@ -149,7 +149,7 @@ def test_verify_data_column_sidecar_inclusion_proof__valid(spec, state): assert spec.verify_data_column_sidecar_inclusion_proof(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_inclusion_proof__invalid_missing_commitment(spec, state): @@ -158,7 +158,7 @@ def test_verify_data_column_sidecar_inclusion_proof__invalid_missing_commitment( assert not spec.verify_data_column_sidecar_inclusion_proof(sidecar) -@with_eip7594_and_later +@with_fulu_and_later @spec_state_test @single_phase def test_verify_data_column_sidecar_inclusion_proof__invalid_duplicate_commitment(spec, state): @@ -170,7 +170,7 @@ def test_verify_data_column_sidecar_inclusion_proof__invalid_duplicate_commitmen # Tests for compute_subnet_for_data_column_sidecar -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase def test_compute_subnet_for_data_column_sidecar(spec): diff --git a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_security.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_security.py similarity index 93% rename from tests/core/pyspec/eth2spec/test/eip7594/unittests/test_security.py rename to tests/core/pyspec/eth2spec/test/fulu/unittests/test_security.py index 282433b9c9..d93560d037 100644 --- a/tests/core/pyspec/eth2spec/test/eip7594/unittests/test_security.py +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_security.py @@ -1,7 +1,7 @@ from eth2spec.test.context import ( spec_test, single_phase, - with_eip7594_and_later, + with_fulu_and_later, with_phases, ) from eth2spec.test.helpers.constants import ( @@ -9,7 +9,7 @@ ) -@with_eip7594_and_later +@with_fulu_and_later @spec_test @single_phase @with_phases([MAINNET]) diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py index c65414b02b..f89b1ff527 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/blob.py +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -3,7 +3,8 @@ from rlp.sedes import Binary, CountableList, List as RLPList, big_endian_int, binary from eth2spec.test.helpers.forks import ( - is_post_eip7594, + is_post_electra, + is_post_fulu, ) @@ -106,7 +107,9 @@ def get_sample_blob_tx(spec, blob_count=1, rng=random.Random(5566), is_valid_blo def get_max_blob_count(spec): - if is_post_eip7594(spec): - return spec.config.MAX_BLOBS_PER_BLOCK_EIP7594 + if is_post_fulu(spec): + return spec.config.MAX_BLOBS_PER_BLOCK_FULU + elif is_post_electra(spec): + return spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA else: return spec.config.MAX_BLOBS_PER_BLOCK diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 284dc32f03..627243be87 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -18,8 +18,8 @@ CUSTODY_GAME = SpecForkName('custody_game') DAS = SpecForkName('das') ELECTRA = SpecForkName('electra') +FULU = SpecForkName('fulu') WHISK = SpecForkName('whisk') -EIP7594 = SpecForkName('eip7594') EIP7732 = SpecForkName('eip7732') # @@ -37,12 +37,12 @@ *MAINNET_FORKS, ELECTRA, # Experimental patches - EIP7594, + FULU, ) # The forks that have light client specs LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0], ELECTRA) # The forks that output to the test vectors. -TESTGEN_FORKS = (*MAINNET_FORKS, ELECTRA, EIP7594, WHISK) +TESTGEN_FORKS = (*MAINNET_FORKS, ELECTRA, FULU, WHISK) # Forks allowed in the test runner `--fork` flag, to fail fast in case of typos ALLOWED_TEST_RUNNER_FORKS = (*ALL_PHASES, WHISK, EIP7732) @@ -57,7 +57,7 @@ ELECTRA: DENEB, # Experimental patches WHISK: CAPELLA, - EIP7594: ELECTRA, + FULU: ELECTRA, EIP7732: ELECTRA, } diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 69e1be669b..fa900a656b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -23,11 +23,15 @@ prepare_state_and_deposit, prepare_deposit_request, ) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash_for_block, +) from eth2spec.test.helpers.proposer_slashings import ( get_valid_proposer_slashing, ) from eth2spec.test.helpers.forks import ( get_next_fork_transition, + is_post_bellatrix, is_post_electra, ) from eth2spec.test.helpers.state import ( @@ -57,13 +61,15 @@ class OperationType(Enum): CONSOLIDATION_REQUEST = auto() -def _set_operations_by_dict(block, operation_dict): +def _set_operations_by_dict(spec, block, operation_dict): for key, value in operation_dict.items(): # to handle e.g. `execution_requests.deposits` and `deposits` obj = block.body for attr in key.split('.')[:-1]: obj = getattr(obj, attr) setattr(obj, key.split('.')[-1], value) + if is_post_bellatrix(spec): + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) def _state_transition_and_sign_block_at_slot(spec, @@ -87,7 +93,7 @@ def _state_transition_and_sign_block_at_slot(spec, block.body.sync_aggregate = sync_aggregate if operation_dict: - _set_operations_by_dict(block, operation_dict) + _set_operations_by_dict(spec, block, operation_dict) assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -403,7 +409,7 @@ def _check_state(): if is_right_before_fork: # add a block with operation. block = build_empty_block_for_next_slot(spec, state) - _set_operations_by_dict(block, operation_dict) + _set_operations_by_dict(spec, block, operation_dict) signed_block = state_transition_and_sign_block(spec, state, block) blocks.append(pre_tag(signed_block)) diff --git a/tests/core/pyspec/eth2spec/test/helpers/forks.py b/tests/core/pyspec/eth2spec/test/helpers/forks.py index e261e3a754..4bb3d54ed3 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/forks.py +++ b/tests/core/pyspec/eth2spec/test/helpers/forks.py @@ -1,6 +1,6 @@ from .constants import ( PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, - ELECTRA, WHISK, EIP7732, EIP7594, + ELECTRA, FULU, WHISK, EIP7732, PREVIOUS_FORK_OF, ) @@ -41,12 +41,12 @@ def is_post_electra(spec): return is_post_fork(spec.fork, ELECTRA) -def is_post_whisk(spec): - return is_post_fork(spec.fork, WHISK) +def is_post_fulu(spec): + return is_post_fork(spec.fork, FULU) -def is_post_eip7594(spec): - return is_post_fork(spec.fork, EIP7594) +def is_post_whisk(spec): + return is_post_fork(spec.fork, WHISK) def is_post_eip7732(spec): diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index 9943fcdd45..4d6e8a2138 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -257,3 +257,96 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): yield 'blocks', [signed_block] yield 'post', state + + +def get_random_execution_requests(spec, state, rng): + deposits = get_random_deposit_requests(spec, state, rng) + withdrawals = get_random_withdrawal_requests(spec, state, rng) + consolidations = get_random_consolidation_requests(spec, state, rng) + + execution_requests = spec.ExecutionRequests( + deposits=deposits, + withdrawals=withdrawals, + consolidations=consolidations + ) + + return execution_requests + + +def get_random_deposit_requests(spec, state, rng, num_deposits=None): + if num_deposits is None: + num_deposits = rng.randint(0, spec.MAX_DEPOSIT_REQUESTS_PER_PAYLOAD) + + deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))] + + deposit_requests = [] + for _ in range(num_deposits): + index = rng.randrange(0, num_deposits) + withdrawal_pubkey = pubkeys[index] + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:] + deposit, _, _ = build_deposit( + spec, + deposit_data_leaves, + pubkeys[index], + privkeys[index], + rng.randint(spec.EFFECTIVE_BALANCE_INCREMENT, 2 * spec.MAX_EFFECTIVE_BALANCE_ELECTRA), + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + deposit_requests.append(spec.DepositRequest( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + signature=deposit.data.signature, + index=rng.randrange(0, 2**64), + )) + + return deposit_requests + + +def get_random_withdrawal_requests(spec, state, rng, num_withdrawals=None): + if num_withdrawals is None: + num_withdrawals = rng.randint(0, spec.MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD) + + current_epoch = spec.get_current_epoch(state) + active_validator_indices = spec.get_active_validator_indices(state, current_epoch) + + withdrawal_requests = [] + for _ in range(num_withdrawals): + if not active_validator_indices: + break + + address = rng.getrandbits(160).to_bytes(20, 'big') + validator_index = rng.choice(active_validator_indices) + validator = state.validators[validator_index] + validator_balance = state.balances[validator_index] + withdrawal_requests.append(spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator.pubkey, + amount=rng.randint(0, validator_balance), + )) + + return withdrawal_requests + + +def get_random_consolidation_requests(spec, state, rng, num_consolidations=None): + if num_consolidations is None: + num_consolidations = rng.randint(0, spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD) + + current_epoch = spec.get_current_epoch(state) + active_validator_indices = spec.get_active_validator_indices(state, current_epoch) + + consolidation_requests = [] + for _ in range(num_consolidations): + source_address = rng.getrandbits(160).to_bytes(20, 'big') + source_index = rng.choice(active_validator_indices) + target_index = rng.choice(active_validator_indices) + source_validator = state.validators[source_index] + target_validator = state.validators[target_index] + consolidation_requests.append(spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=source_validator.pubkey, + target_pubkey=target_validator.pubkey, + )) + + return consolidation_requests diff --git a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py index 71f8b5ebb8..1c9dec0060 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py @@ -120,7 +120,7 @@ def prepare_pending_withdrawal(spec, state, validator_index, ) withdrawal = spec.PendingPartialWithdrawal( - index=validator_index, + validator_index=validator_index, amount=amount, withdrawable_epoch=withdrawable_epoch, ) @@ -238,7 +238,7 @@ def run_withdrawals_processing(spec, state, execution_payload, num_expected_with assert len(pending_withdrawal_requests) <= len(execution_payload.withdrawals) for index, request in enumerate(pending_withdrawal_requests): withdrawal = execution_payload.withdrawals[index] - assert withdrawal.validator_index == request.index + assert withdrawal.validator_index == request.validator_index assert withdrawal.amount == request.amount return expected_withdrawals diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py index ed584ed612..2a09617302 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py @@ -1,8 +1,9 @@ from eth2spec.test.context import ( + PHASE0, single_phase, spec_test, with_presets, - with_all_phases, + with_phases, ) from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.deposits import ( @@ -26,7 +27,7 @@ def eth1_init_data(eth1_block_hash, eth1_timestamp): } -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -62,7 +63,7 @@ def test_initialize_beacon_state_from_eth1(spec): yield 'state', state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -113,7 +114,7 @@ def test_initialize_beacon_state_some_small_balances(spec): yield 'state', state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -162,7 +163,7 @@ def test_initialize_beacon_state_one_topup_activation(spec): yield 'state', state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -189,7 +190,7 @@ def test_initialize_beacon_state_random_invalid_genesis(spec): yield 'state', state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py index d245b8fcf4..3304deebd8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py @@ -1,8 +1,9 @@ from eth2spec.test.context import ( + PHASE0, spec_test, single_phase, with_presets, - with_all_phases, + with_phases, ) from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.deposits import ( @@ -43,7 +44,7 @@ def run_is_valid_genesis_state(spec, state, valid=True): assert is_valid == valid -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -56,7 +57,7 @@ def test_full_genesis_deposits(spec): yield from run_is_valid_genesis_state(spec, state) -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -70,7 +71,7 @@ def test_invalid_invalid_timestamp(spec): yield from run_is_valid_genesis_state(spec, state, valid=False) -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -84,7 +85,7 @@ def test_extra_balance(spec): yield from run_is_valid_genesis_state(spec, state) -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") @@ -107,7 +108,7 @@ def test_one_more_validator(spec): yield from run_is_valid_genesis_state(spec, state) -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") diff --git a/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py b/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py index 071efd4a2e..7566e9c981 100644 --- a/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py @@ -6,7 +6,7 @@ ) from eth2spec.utils import bls -from eth2spec.eip7594 import spec +from eth2spec.fulu import spec ############################################################################### diff --git a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py index 93bb3b204c..0e4727b794 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -16,6 +16,7 @@ get_random_bls_to_execution_changes, get_random_sync_aggregate, prepare_state_and_get_random_deposits, + get_random_execution_requests, ) from eth2spec.test.helpers.inactivity_scores import ( randomize_inactivity_scores, @@ -250,7 +251,7 @@ def random_block_capella(spec, state, signed_blocks, scenario_state, rng=Random( def random_block_deneb(spec, state, signed_blocks, scenario_state, rng=Random(3456)): block = random_block_capella(spec, state, signed_blocks, scenario_state, rng=rng) # TODO: more commitments. blob_kzg_commitments: List[KZGCommitment, MAX_BLOBS_PER_BLOCK] - # TODO: add MAX_BLOBS_PER_BLOCK_EIP7594 at fulu + # TODO: add MAX_BLOBS_PER_BLOCK_FULU at fulu opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( spec, blob_count=rng.randint(0, spec.config.MAX_BLOBS_PER_BLOCK), rng=rng) block.body.execution_payload.transactions.append(opaque_tx) @@ -262,6 +263,7 @@ def random_block_deneb(spec, state, signed_blocks, scenario_state, rng=Random(34 def random_block_electra(spec, state, signed_blocks, scenario_state, rng=Random(3456)): block = random_block_deneb(spec, state, signed_blocks, scenario_state, rng=rng) + block.body.execution_requests = get_random_execution_requests(spec, state, rng=rng) return block diff --git a/tests/formats/README.md b/tests/formats/README.md index ec495daa5b..4811595ba8 100644 --- a/tests/formats/README.md +++ b/tests/formats/README.md @@ -50,7 +50,7 @@ Test formats: ## Glossary - `generator`: a program that outputs one or more test-cases, each organized into a `config > runner > handler > suite` hierarchy. -- `config`: tests are grouped by configuration used for spec presets. In addition to the standard configurations, +- `config`: tests are grouped by configuration used for spec presets. In addition to the standard configurations, `general` may be used as a catch-all for tests not restricted to one configuration. (E.g. BLS). - `type`: the specialization of one single `generator`. E.g. epoch processing. - `runner`: where a generator is a *"producer"*, this is the *"consumer"*. @@ -59,10 +59,10 @@ Test formats: To facilitate this, you specify a `handler`: the runner can deal with the format by using the specified handler. - `suite`: a directory containing test cases that are coherent. Each `suite` under the same `handler` shares the same format. This is an organizational/cosmetic hierarchy layer. -- `case`: a test case, a directory in a `suite`. A case can be anything in general, +- `case`: a test case, a directory in a `suite`. A case can be anything in general, but its format should be well-defined in the documentation corresponding to the `type` (and `handler`). - `case part`: a test case consists of different files, possibly in different formats, to facilitate the specific test case format better. - Optionally, a `meta.yaml` is included to declare meta-data for the test, e.g. BLS requirements. + Optionally, a `meta.yaml` is included to declare meta-data for the test, e.g. BLS requirements. ## Test format philosophy @@ -70,12 +70,12 @@ Test formats: The configuration constant types are: - Never changing: genesis data. -- Changing, but reliant on old value: e.g. an epoch time may change, but if you want to do the conversion +- Changing, but reliant on old value: e.g. an epoch time may change, but if you want to do the conversion `(genesis data, timestamp) -> epoch number`, you end up needing both constants. - Changing, but kept around during fork transition: finalization may take a while, e.g. an executable has to deal with new deposits and old deposits at the same time. Another example may be economic constants. - Additional, backwards compatible: new constants are introduced for later phases. -- Changing: there is a very small chance some constant may really be *replaced*. +- Changing: there is a very small chance some constant may really be *replaced*. In this off-chance, it is likely better to include it as an additional variable, and some clients may simply stop supporting the old one if they do not want to sync from genesis. The change of functionality goes through a phase of deprecation of the old constant, and eventually only the new constant is kept around in the config (when old state is not supported anymore). @@ -157,7 +157,7 @@ Between all types of tests, a few formats are common: ##### `meta.yaml` -If present (it is optional), the test is enhanced with extra data to describe usage. Specialized data is described in the documentation of the specific test format. +If present (it is optional), the test is enhanced with extra data to describe usage. Specialized data is described in the documentation of the specific test format. Common data is documented here: @@ -205,7 +205,7 @@ The basic pattern for test-suite loading and running is: 1. For a specific config, load it first (and only need to do so once), then continue with the tests defined in the config folder. -2. Select a fork. Repeat for each fork if running tests for multiple forks. +2. Select a fork. Repeat for each fork if running tests for multiple forks. 3. Select the category and specialization of interest (e.g. `operations > deposits`). Again, repeat for each if running all. 4. Select a test suite. Or repeat for each. 5. Select a test case. Or repeat for each. @@ -213,4 +213,4 @@ The basic pattern for test-suite loading and running is: 7. Run the test, as defined by the test format. Step 1 may be a step with compile time selection of a configuration, if desired for optimization. -The base requirement is just to use the same set of constants, independent of the loading process. +The base requirement is just to use the same set of constants, independent of the loading process. diff --git a/tests/formats/epoch_processing/README.md b/tests/formats/epoch_processing/README.md index 0f6642141a..652cae0e91 100644 --- a/tests/formats/epoch_processing/README.md +++ b/tests/formats/epoch_processing/README.md @@ -25,7 +25,7 @@ An SSZ-snappy encoded `BeaconState`, the state after applying the epoch sub-tran ## Condition -A handler of the `epoch_processing` test-runner should process these cases, +A handler of the `epoch_processing` test-runner should process these cases, calling the corresponding processing implementation (same name, prefixed with `process_`). This excludes the other parts of the epoch-transition. The provided pre-state is already transitioned to just before the specific sub-transition of focus of the handler. diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index 258dfe433d..58709b3fee 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -90,7 +90,7 @@ The parameter that is required for executing `on_block(store, block)`. proofs: array of byte48 hex string -- optional, the proofs of blob commitments. valid: bool -- optional, default to `true`. If it's `false`, this execution step is expected to be invalid. -} +} ``` The file is located in the same folder (see below). diff --git a/tests/formats/networking/README.md b/tests/formats/networking/README.md index e4679c17eb..57effda266 100644 --- a/tests/formats/networking/README.md +++ b/tests/formats/networking/README.md @@ -3,4 +3,5 @@ The aim of the networking tests is to set a base-line on what really needs to pass, i.e. the essentials. Handlers: -- [`get_custody_columns`](./get_custody_columns.md): `get_custody_columns` helper tests +- [`compute_columns_for_custody_group`](./compute_columns_for_custody_group.md): `compute_columns_for_custody_group` helper tests +- [`get_custody_groups`](./get_custody_groups.md): `get_custody_groups` helper tests diff --git a/tests/formats/networking/compute_columns_for_custody_group.md b/tests/formats/networking/compute_columns_for_custody_group.md new file mode 100644 index 0000000000..23e96025de --- /dev/null +++ b/tests/formats/networking/compute_columns_for_custody_group.md @@ -0,0 +1,13 @@ +# `compute_columns_for_custody_group` tests + +`compute_columns_for_custody_group` tests provide sanity checks for the correctness of the `compute_columns_for_custody_group` helper function. + +## Test case format + +### `meta.yaml` + +```yaml +description: string -- optional: description of test case, purely for debugging purposes. +custody_group: int -- argument: the custody group index. +result: list of int -- output: the list of resulting column indices. +``` diff --git a/tests/formats/networking/get_custody_columns.md b/tests/formats/networking/get_custody_groups.md similarity index 53% rename from tests/formats/networking/get_custody_columns.md rename to tests/formats/networking/get_custody_groups.md index ee0c30859c..46206da85d 100644 --- a/tests/formats/networking/get_custody_columns.md +++ b/tests/formats/networking/get_custody_groups.md @@ -1,6 +1,6 @@ -# `get_custody_columns` tests +# `get_custody_groups` tests -`get_custody_columns` tests provide sanity check of the correctness of `get_custody_columns` helper. +`get_custody_groups` tests provide sanity checks for the correctness of the `get_custody_groups` helper function. ## Test case format @@ -9,6 +9,6 @@ ```yaml description: string -- optional: description of test case, purely for debugging purposes. node_id: int -- argument: the NodeID input. -custody_subnet_count: int -- argument: the count of custody subnets. -result: list of int -- output: the list of resulting column indices. +custody_group_count: int -- argument: the count of custody groups. +result: list of int -- output: the list of resulting custody group indices. ``` diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md index a6682042f7..c7f3a9581b 100644 --- a/tests/formats/rewards/README.md +++ b/tests/formats/rewards/README.md @@ -49,7 +49,7 @@ An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned b ## Condition -A handler of the `rewards` test-runner should process these cases, +A handler of the `rewards` test-runner should process these cases, calling the corresponding rewards deltas function for each set of deltas. The provided pre-state is ready to be input into each rewards deltas function. diff --git a/tests/formats/shuffling/README.md b/tests/formats/shuffling/README.md index 15bfe6996b..89d64e24de 100644 --- a/tests/formats/shuffling/README.md +++ b/tests/formats/shuffling/README.md @@ -35,4 +35,4 @@ I.e. `mapping[i]` is the shuffled location of `i`. ## Condition The resulting list should match the expected output after shuffling the implied input, using the given `seed`. -The output is checked using the `mapping`, based on the shuffling test type (e.g. can be backwards shuffling). +The output is checked using the `mapping`, based on the shuffling test type (e.g. can be backwards shuffling). diff --git a/tests/formats/ssz_generic/README.md b/tests/formats/ssz_generic/README.md index c46025847a..3545ab28c9 100644 --- a/tests/formats/ssz_generic/README.md +++ b/tests/formats/ssz_generic/README.md @@ -59,7 +59,7 @@ The object, encoded as a YAML structure. Using the same familiar encoding as YAM The conditions are the same for each type: - Encoding: After encoding the given `value` object, the output should match `serialized`. -- Decoding: After decoding the given `serialized` bytes, it should match the `value` object. +- Decoding: After decoding the given `serialized` bytes, it should match the `value` object. - Hash-tree-root: the root should match the root declared in the metadata. ## `invalid` diff --git a/tests/formats/ssz_static/core.md b/tests/formats/ssz_static/core.md index 09ff04e20d..a198bbcaff 100644 --- a/tests/formats/ssz_static/core.md +++ b/tests/formats/ssz_static/core.md @@ -42,7 +42,7 @@ A test-runner can implement the following assertions: - Serialization: After parsing the `value`, SSZ-serialize it: the output should match `serialized` - Deserialization: SSZ-deserialize the `serialized` value, and see if it matches the parsed `value` - If YAML decoding of SSZ objects is not supported by the implementation: - - Serialization in 2 steps: deserialize `serialized`, then serialize the result, + - Serialization in 2 steps: deserialize `serialized`, then serialize the result, and verify if the bytes match the original `serialized`. - Hash-tree-root: After parsing the `value` (or deserializing `serialized`), Hash-tree-root it: the output should match `root` diff --git a/tests/generators/README.md b/tests/generators/README.md index 148415f4de..270d107ea5 100644 --- a/tests/generators/README.md +++ b/tests/generators/README.md @@ -7,7 +7,7 @@ Any issues with the generators and/or generated tests should be filed in the rep On releases, test generators are run by the release manager. Test-generation of mainnet tests can take a significant amount of time, and is better left out of a CI setup. -An automated nightly tests release system, with a config filter applied, is being considered as implementation needs mature. +An automated nightly tests release system, with a config filter applied, is being considered as implementation needs mature. ## Table of contents @@ -39,7 +39,7 @@ Prerequisites: This removes the existing virtual environments (`/tests/generators//venv`) and generated tests (`../consensus-spec-tests/tests`). ```bash -make clean +make clean && rm -rf ../consensus-spec-tests/tests ``` ### Running all test generators @@ -47,7 +47,7 @@ make clean This runs all of the generators. ```bash -make -j 4 generate_tests +make -j 4 gen_all ``` The `-j N` flag makes the generators run in parallel, with `N` being the amount of cores. @@ -63,39 +63,12 @@ make gen_ssz_static ## Developing a generator -Simply open up the generator (not all at once) of choice in your favorite IDE/editor and run: - -```bash -# From the root of the generator directory: -# Create a virtual environment (any venv/.venv/.venvs is git-ignored) -python3 -m venv venv -# Activate the venv, this is where dependencies are installed for the generator -. venv/bin/activate -``` - -Now that you have a virtual environment, write your generator. -It's recommended to extend the base-generator. - -Create a `requirements.txt` in the root of your generator directory: -``` -pytest>=4.4 -../../../[generator] -``` - The config helper and pyspec is optional, but preferred. We encourage generators to derive tests from the spec itself in order to prevent code duplication and outdated tests. Applying configurations to the spec is simple and enables you to create test suites with different contexts. *Note*: Make sure to run `make pyspec` from the root of the specs repository in order to build the pyspec requirement. -Install all the necessary requirements (re-run when you add more): -```bash -pip3 install -r requirements.txt -``` - -Note that you may need `PYTHONPATH` to include the pyspec directory, as with running normal tests, - to run test generators manually. The makefile handles this for you already. - -And write your initial test generator, extending the base generator: +Write your initial test generator, extending the base generator: Write a `main.py` file. The shuffling test generator is a good minimal starting point: @@ -196,9 +169,9 @@ Recommendations: - You can have more than just one test provider. - Your test provider is free to output any configuration and combination of runner/handler/fork/case name. - You can split your test case generators into different Python files/packages; this is good for code organization. -- Use config `minimal` for performance and simplicity, but also implement a suite with the `mainnet` config where necessary. +- Use config `minimal` for performance and simplicity, but also implement a suite with the `mainnet` config where necessary. - You may be able to write your test case provider in a way where it does not make assumptions on constants. - If so, you can generate test cases with different configurations for the same scenario (see example). + If so, you can generate test cases with different configurations for the same scenario (see example). - See [`tests/core/gen_helpers/README.md`](../core/pyspec/eth2spec/gen_helpers/README.md) for command line options for generators. ## How to add a new test generator diff --git a/tests/generators/epoch_processing/README.md b/tests/generators/epoch_processing/README.md index 662b0b516d..c572993d23 100644 --- a/tests/generators/epoch_processing/README.md +++ b/tests/generators/epoch_processing/README.md @@ -7,5 +7,5 @@ An epoch-processing test-runner can consume these sub-transition test-suites, Information on the format of the tests can be found in the [epoch-processing test formats documentation](../../formats/epoch_processing/README.md). - + diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py index 9e82d1c112..12907ab7de 100644 --- a/tests/generators/genesis/main.py +++ b/tests/generators/genesis/main.py @@ -1,4 +1,4 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods, check_mods +from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA @@ -8,13 +8,8 @@ 'validity', ]} - altair_mods = phase_0_mods - - # we have new unconditional lines in `initialize_beacon_state_from_eth1` and we want to test it - _new_bellatrix_mods = {key: 'eth2spec.test.bellatrix.genesis.test_' + key for key in [ - 'initialization', - ]} - bellatrix_mods = combine_mods(_new_bellatrix_mods, altair_mods) + altair_mods = phase_0_mods # No additional Altair specific genesis tests + bellatrix_mods = altair_mods # No additional Bellatrix specific genesis tests capella_mods = bellatrix_mods # No additional Capella specific genesis tests deneb_mods = capella_mods # No additional Deneb specific genesis tests electra_mods = deneb_mods # No additional Electra specific genesis tests diff --git a/tests/generators/kzg_4844/main.py b/tests/generators/kzg_4844/main.py index a4d3544bdf..2e125ba9d9 100644 --- a/tests/generators/kzg_4844/main.py +++ b/tests/generators/kzg_4844/main.py @@ -598,7 +598,6 @@ def cases_fn() -> Iterable[gen_typing.TestCase]: if __name__ == "__main__": bls.use_arkworks() gen_runner.run_generator("kzg", [ - # DENEB create_provider(DENEB, 'blob_to_kzg_commitment', case01_blob_to_kzg_commitment), create_provider(DENEB, 'compute_kzg_proof', case02_compute_kzg_proof), create_provider(DENEB, 'verify_kzg_proof', case03_verify_kzg_proof), diff --git a/tests/generators/kzg_7594/main.py b/tests/generators/kzg_7594/main.py index 814a840537..0c7222fd21 100644 --- a/tests/generators/kzg_7594/main.py +++ b/tests/generators/kzg_7594/main.py @@ -6,9 +6,9 @@ from eth_utils import encode_hex -from eth2spec.eip7594 import spec +from eth2spec.fulu import spec from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from eth2spec.test.helpers.constants import EIP7594 +from eth2spec.test.helpers.constants import FULU from eth2spec.test.helpers.typing import SpecForkName from eth2spec.test.utils.kzg_tests import ( CELL_RANDOM_VALID1, @@ -565,8 +565,7 @@ def cases_fn() -> Iterable[gen_typing.TestCase]: if __name__ == "__main__": bls.use_arkworks() gen_runner.run_generator("kzg_7594", [ - # EIP-7594 - create_provider(EIP7594, 'compute_cells_and_kzg_proofs', case_compute_cells_and_kzg_proofs), - create_provider(EIP7594, 'verify_cell_kzg_proof_batch', case_verify_cell_kzg_proof_batch), - create_provider(EIP7594, 'recover_cells_and_kzg_proofs', case_recover_cells_and_kzg_proofs), + create_provider(FULU, 'compute_cells_and_kzg_proofs', case_compute_cells_and_kzg_proofs), + create_provider(FULU, 'verify_cell_kzg_proof_batch', case_verify_cell_kzg_proof_batch), + create_provider(FULU, 'recover_cells_and_kzg_proofs', case_recover_cells_and_kzg_proofs), ]) diff --git a/tests/generators/merkle_proof/main.py b/tests/generators/merkle_proof/main.py index 9ae985b865..4a787f5737 100644 --- a/tests/generators/merkle_proof/main.py +++ b/tests/generators/merkle_proof/main.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import DENEB, ELECTRA, EIP7594 +from eth2spec.test.helpers.constants import DENEB, ELECTRA, FULU from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, combine_mods, check_mods @@ -6,16 +6,16 @@ deneb_mods = {key: 'eth2spec.test.deneb.merkle_proof.test_' + key for key in [ 'single_merkle_proof', ]} - _new_eip7594_mods = {key: 'eth2spec.test.eip7594.merkle_proof.test_' + key for key in [ + electra_mods = deneb_mods + _new_fulu_mods = {key: 'eth2spec.test.fulu.merkle_proof.test_' + key for key in [ 'single_merkle_proof', ]} - electra_mods = deneb_mods - eip_7594_mods = combine_mods(_new_eip7594_mods, electra_mods) + fulu_mods = combine_mods(_new_fulu_mods, electra_mods) all_mods = { DENEB: deneb_mods, ELECTRA: electra_mods, - EIP7594: eip_7594_mods, + FULU: fulu_mods, } check_mods(all_mods, "merkle_proof") diff --git a/tests/generators/networking/main.py b/tests/generators/networking/main.py index 52b94929f7..3217c2cce2 100644 --- a/tests/generators/networking/main.py +++ b/tests/generators/networking/main.py @@ -1,14 +1,14 @@ -from eth2spec.test.helpers.constants import EIP7594 +from eth2spec.test.helpers.constants import FULU from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators, check_mods if __name__ == "__main__": - eip7594_mods = {key: 'eth2spec.test.eip7594.networking.test_' + key for key in [ + fulu_mods = {key: 'eth2spec.test.fulu.networking.test_' + key for key in [ 'get_custody_columns', ]} all_mods = { - EIP7594: eip7594_mods + FULU: fulu_mods } check_mods(all_mods, "networking") diff --git a/tests/generators/operations/README.md b/tests/generators/operations/README.md index a5d48c11b4..234bb92a82 100644 --- a/tests/generators/operations/README.md +++ b/tests/generators/operations/README.md @@ -8,5 +8,5 @@ An operation test-runner can consume these operation test-suites, Information on the format of the tests can be found in the [operations test formats documentation](../../formats/operations/README.md). - + diff --git a/tests/generators/sanity/README.md b/tests/generators/sanity/README.md index cbc6aef06d..61979976db 100644 --- a/tests/generators/sanity/README.md +++ b/tests/generators/sanity/README.md @@ -4,5 +4,5 @@ Sanity tests cover regular state-transitions in a common block-list format, to e Information on the format of the tests can be found in the [sanity test formats documentation](../../formats/sanity/README.md). - + diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 8039b82a44..2101894d9c 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -31,9 +31,13 @@ # This is a "hack" which allows other test files (e.g., test_deposit_transition.py) # to reuse the sanity/block test format. If a new test file is added or removed, # do not forget to update sanity/block/__init__.py accordingly. - _new_electra_mods = {key: 'eth2spec.test.electra.sanity.' + key for key in [ + _new_electra_mods_1 = {key: 'eth2spec.test.electra.sanity.' + key for key in [ 'blocks', ]} + _new_electra_mods_2 = {key: 'eth2spec.test.electra.sanity.test_' + key for key in [ + 'slots', + ]} + _new_electra_mods = {**_new_electra_mods_1, **_new_electra_mods_2} electra_mods = combine_mods(_new_electra_mods, deneb_mods) all_mods = {